├── .github └── FUNDING.yml ├── .gitignore ├── ArtnetESP32.cpp ├── ArtnetESP32.h ├── LICENSE ├── README.md ├── UdpArtnet.cpp ├── UdpArtnet.h └── examples ├── SD-Artnet └── SD-Artnet.ino ├── exampleArtnet └── exampleArtnet.ino └── readSD └── readSD.ino /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: hpwit # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Compiled Dynamic libraries 8 | *.so 9 | *.dylib 10 | *.dll 11 | 12 | # Compiled Static libraries 13 | *.lai 14 | *.la 15 | *.a 16 | *.lib 17 | 18 | # Executables 19 | *.exe 20 | *.out 21 | *.app 22 | 23 | # Osx 24 | .DS_Store 25 | 26 | # Sublime 27 | *.sublime-workspace -------------------------------------------------------------------------------- /ArtnetESP32.cpp: -------------------------------------------------------------------------------- 1 | /*The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nathanaël Lécaudé 4 | https://github.com/natcl/Artnet, http://forum.pjrc.com/threads/24688-Artnet-to-OctoWS2811 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #define BUFFER_SIZE 800 29 | 30 | ArtnetESP32::ArtnetESP32() {} 31 | 32 | uint32_t ArtnetESP32::getsync() 33 | { 34 | return sync; 35 | } 36 | 37 | void ArtnetESP32::resetsync() 38 | { 39 | sync = 0; 40 | sync2 = 0; 41 | } 42 | 43 | uint8_t *ArtnetESP32::getframe(int framenumber) 44 | { 45 | /*if(framenumber<2) 46 | return frames[framenumber]; 47 | else*/ 48 | return NULL; 49 | } 50 | void ArtnetESP32::getframe(uint8_t *leds) 51 | { 52 | memcpy(leds, &artnetleds1[((currentframenumber + 1) % 2) * nbPixels * 3], nbPixels * 3); 53 | } 54 | 55 | uint8_t *ArtnetESP32::getframe() 56 | { 57 | uint8_t rd = readbuffer; 58 | readbuffer = (readbuffer + 1) % buffernum; 59 | nbframeread++; 60 | // Serial.printf("Read framebuff:%d\n",rd); 61 | return &artnetleds1[nbPixels * 3 * rd]; 62 | } 63 | 64 | void ArtnetESP32::begin(byte mac[], byte ip[]) 65 | { 66 | #if !defined(ARDUINO_SAMD_ZERO) && !defined(ESP8266) && !defined(ESP32) 67 | Ethernet.begin(mac, ip); 68 | #endif 69 | 70 | Udp.begin(ART_NET_PORT); 71 | } 72 | 73 | void ArtnetESP32::stop() 74 | { 75 | if (running) 76 | { 77 | Udp.stop(); 78 | //for(int i=0;i1) 182 | // { 183 | // for(int uni=0;uni 0) 201 | { 202 | uint32_t waitnewtframe = 0; 203 | playingfile.read(getframeread(buffer_read), nbPixels * 3 + 4); 204 | waitnewtframe = *((uint32_t *)getframeread(buffer_read)); 205 | totalrecording += waitnewtframe / 240; 206 | uint32_t time_now = ESP.getCycleCount(); 207 | uint32_t diff = (time_now - timenow); 208 | if (waitnewtframe > diff) 209 | delayMicroseconds((waitnewtframe - diff) / 240); 210 | 211 | timenow = ESP.getCycleCount(); 212 | buffer_play = buffer_read; 213 | xTaskNotifyGive(readFromSDHandle); 214 | buffer_read++; 215 | return true; 216 | } 217 | else 218 | { 219 | 220 | Serial.printf("Reading done nbframes:%d totlrecord %ld ms\n", buffer_read, totalrecording / 1000); 221 | buffer_read = 0; 222 | buffer_play = 0; 223 | totalrecording = 0; 224 | return false; 225 | } 226 | } 227 | 228 | void ArtnetESP32::getFrameForRecord(uint8_t *leds) 229 | { 230 | uint32_t size = nbPixelsPerUniverse * 3; 231 | uint32_t decal = nbPixelsPerUniverse * 3 + ART_DMX_START; 232 | uint32_t decal2 = nbNeededUniverses * decal; 233 | uint8_t *offset; 234 | recordfile.write((uint8_t *)(&elaspe[nbframeread % 2]), 4); 235 | offset = artnetleds1 + (nbframeread % 2) * decal2 + ART_DMX_START; 236 | if (nbNeededUniverses > 1) 237 | { 238 | for (int uni = 0; uni < nbNeededUniverses - 1; uni++) 239 | { 240 | memcpy(leds, offset, size); 241 | offset += decal; 242 | leds += size; 243 | } 244 | } 245 | memcpy(leds, offset, last_size); 246 | //For an unknown reason if you put the write here, only couple of leds are actually copied ... 247 | //recordfile.write(leds,nbPixels*3); 248 | } 249 | 250 | uint8_t *ArtnetESP32::getFrameReadSD() 251 | { 252 | return getframeread(buffer_play) + 4; 253 | } 254 | 255 | uint8_t *ArtnetESP32::getframeread(int buffer) 256 | { 257 | return artnetleds1 + (buffer % 2) * (nbPixelsPerUniverse * 3 * nbNeededUniverses + 4); 258 | } 259 | 260 | void ArtnetESP32::getBufferFrame(uint8_t *leds) 261 | { 262 | uint32_t size = nbPixelsPerUniverse * 3; 263 | uint32_t decal = nbPixelsPerUniverse * 3 + ART_DMX_START; 264 | uint32_t decal2 = nbNeededUniverses * decal; 265 | // uint8_t * udpof=Udp.udpBuffer + ART_DMX_START; 266 | uint8_t *offset; 267 | offset = artnetleds1 + ((currentframenumber + 1) % 2) * decal2 + ART_DMX_START; 268 | 269 | if (nbNeededUniverses > 1) 270 | { 271 | for (int uni = 0; uni < nbNeededUniverses - 1; uni++) 272 | { 273 | memcpy(leds, offset, size); 274 | offset += decal; 275 | leds += size; 276 | } 277 | } 278 | memcpy(leds, offset, last_size); 279 | nbframeread++; 280 | } 281 | 282 | uint32_t ArtnetESP32::getElaspseTime() 283 | { 284 | return elaspe[(currentframenumber + 1) % 2]; 285 | } 286 | 287 | uint16_t ArtnetESP32::readWithoutWaiting() 288 | { 289 | struct sockaddr_in si_other; 290 | int slen = sizeof(si_other), len; 291 | long timef = 0; 292 | 293 | //timef=millis(); 294 | incomingUniverse = 99; 295 | uint32_t decal = nbPixelsPerUniverse * 3 + ART_DMX_START; 296 | uint32_t decal2 = nbNeededUniverses * decal; 297 | uint8_t *offset; 298 | bool resetframe = true; 299 | 300 | timef = millis(); 301 | offset = artnetleds1 + currentframenumber * decal2; 302 | while (incomingUniverse != nbNeededUniverses - 1) 303 | { 304 | if (millis() - timef > 1000) 305 | { 306 | Serial.println("Time out fired"); 307 | return 0; 308 | } 309 | 310 | if ((len = recvfrom(Udp.udp_server, offset, BUFFER_SIZE, MSG_DONTWAIT, (struct sockaddr *)&si_other, (socklen_t *)&slen)) > 0) //1460 311 | { 312 | 313 | incomingUniverse = *(offset + 14); 314 | } 315 | } 316 | } 317 | 318 | uint16_t ArtnetESP32::read4() 319 | { 320 | struct sockaddr_in si_other; 321 | int slen = sizeof(si_other), len; 322 | long timef = 0; 323 | 324 | //timef=millis(); 325 | incomingUniverse = 99; 326 | uint32_t decal = nbPixelsPerUniverse * 3 + ART_DMX_START; 327 | uint32_t decal2 = nbNeededUniverses * decal; 328 | uint8_t *offset; 329 | offset = artnetleds1 + currentframenumber * decal2; 330 | uint8_t ggh[200]; 331 | while (1) 332 | { 333 | int i = 0; 334 | while (i < 200) 335 | { 336 | if ((len = recvfrom(Udp.udp_server, offset, BUFFER_SIZE, MSG_DONTWAIT, (struct sockaddr *)&si_other, (socklen_t *)&slen)) > 0) //1460 337 | { 338 | 339 | ggh[i] = offset[14]; 340 | i++; 341 | } 342 | } 343 | for (int i = 0; i < 200; i++) 344 | { 345 | Serial.printf("%d ", ggh[i]); 346 | } 347 | Serial.println(); 348 | } 349 | } 350 | 351 | 352 | uint16_t ArtnetESP32::read2() 353 | { 354 | struct sockaddr_in si_other; 355 | int slen = sizeof(si_other), len; 356 | long timef = 0; 357 | 358 | //timef=millis(); 359 | incomingUniverse = 99; 360 | uint32_t decal = nbPixelsPerUniverse * 3 + ART_DMX_START; 361 | uint32_t decal2 = nbNeededUniverses * decal; 362 | uint8_t *offset; 363 | bool resetframe = true; 364 | 365 | er: 366 | timef = millis(); 367 | offset = artnetleds1 + currentframenumber * decal2; 368 | 369 | while (incomingUniverse != 0) 370 | { 371 | if (millis() - timef > 1000) 372 | { 373 | Serial.println("Time out fired"); 374 | return 0; 375 | } 376 | if ((len = recvfrom(Udp.udp_server, offset, BUFFER_SIZE, MSG_DONTWAIT, (struct sockaddr *)&si_other, (socklen_t *)&slen)) > 0) //1460 377 | { 378 | 379 | incomingUniverse = offset[14]; 380 | } 381 | } 382 | if (resetframe == true or frameslues == 0) 383 | { 384 | current_time = ESP.getCycleCount(); 385 | if (frameslues == 0) 386 | { 387 | start_time = current_time; 388 | } 389 | elaspe[currentframenumber] = (current_time - start_time); 390 | /*if(elaspe[currentframenumber]/240000<30) 391 | Serial.printf("frame:%d time:%lu\n",frameslues,elaspe[currentframenumber]/240);*/ 392 | start_time = current_time; 393 | } 394 | for (int uni = 1; uni < nbNeededUniverses; uni++) 395 | { 396 | offset += decal; 397 | while ((len = recvfrom(Udp.udp_server, offset, BUFFER_SIZE, MSG_DONTWAIT, (struct sockaddr *)&si_other, (socklen_t *)&slen)) <= 0) 398 | { 399 | if (millis() - timef > 1000) 400 | { 401 | Serial.println("Time out fired"); 402 | return 0; 403 | } 404 | } 405 | incomingUniverse = *(offset + 14); 406 | if (incomingUniverse != uni) 407 | { 408 | lostframes++; 409 | resetframe = false; 410 | goto er; 411 | } 412 | } 413 | Udp.flush(); 414 | currentframenumber = (currentframenumber + 1) % 2; 415 | frameslues++; 416 | return 1; 417 | } 418 | /* 419 | uint16_t ArtnetESP32::readFrame() 420 | { 421 | uint16_t result; 422 | result = read3(); 423 | if (result == 1 and artnetAfterFrameHandle) 424 | xTaskNotifyGive(artnetAfterFrameHandle); 425 | return result; 426 | } 427 | */ 428 | uint16_t ArtnetESP32::readFrameRecord() 429 | { 430 | uint16_t result; 431 | result = read2(); 432 | return result; 433 | } 434 | 435 | uint16_t ArtnetESP32::read2(TaskHandle_t task) 436 | { 437 | struct sockaddr_in si_other; 438 | int slen = sizeof(si_other), len; 439 | long timef = 0; 440 | sync = 0; 441 | sync2 = 0; 442 | timef = millis(); 443 | incomingUniverse = 99; 444 | uint32_t size = nbPixelsPerUniverse * 3; 445 | uint32_t decal = nbPixelsPerUniverse * 3 + ART_DMX_START; 446 | uint32_t decal2 = nbNeededUniverses * decal; 447 | uint8_t *udpof = Udp.udpBuffer + ART_DMX_START; 448 | uint8_t *offset2 = artnetleds1; 449 | offset2 += currentframenumber * decal2; 450 | uint8_t *offset; 451 | timef = millis(); 452 | for (;;) 453 | { 454 | 455 | er: 456 | 457 | offset = offset2; //+currentframenumber*nbPixels*3; 458 | 459 | //er: 460 | 461 | while (incomingUniverse != 0) 462 | { 463 | if (millis() - timef > 1000) 464 | { 465 | Serial.println("Time out fired"); 466 | return 0; 467 | } 468 | if ((len = recvfrom(Udp.udp_server, offset, BUFFER_SIZE, MSG_DONTWAIT, (struct sockaddr *)&si_other, (socklen_t *)&slen)) > 0) //1460 469 | { 470 | 471 | incomingUniverse = offset[14]; //+ Udp.udpBuffer[15]; 472 | } 473 | } 474 | 475 | for (int uni = 1; uni < nbNeededUniverses; uni++) 476 | { 477 | offset += decal; 478 | while ((len = recvfrom(Udp.udp_server, offset, BUFFER_SIZE, MSG_DONTWAIT, (struct sockaddr *)&si_other, (socklen_t *)&slen)) <= 0) 479 | { 480 | if (millis() - timef > 1000) 481 | { 482 | Serial.println("Time out fired"); 483 | return 0; 484 | } 485 | } 486 | incomingUniverse = *(offset + 14); 487 | if (incomingUniverse != uni) 488 | { 489 | lostframes++; 490 | goto er; 491 | } 492 | } 493 | 494 | frameslues++; 495 | Udp.flush(); 496 | currentframenumber = (currentframenumber + 1) % 2; 497 | if (userArtnetHandle == 0) 498 | { 499 | userArtnetHandle = xTaskGetCurrentTaskHandle(); 500 | xTaskNotifyGive(task); 501 | } 502 | //currentframenumber=(currentframenumber+1)%2; 503 | //memcpy(&artnetleds1[nbPixelsPerUniverse*(incomingUniverse)*3+currentframenumber*nbPixels*3],Udp.udpBuffer + ART_DMX_START,nbPixelsPerUniverse*3); 504 | } 505 | 506 | return 1; 507 | } 508 | 509 | void ArtnetESP32::setLedsBuffer(uint8_t *leds) 510 | { 511 | ledsbuffer = leds; 512 | } 513 | 514 | void ArtnetESP32::startArtnetrecord(File file) 515 | { 516 | if (stopRecord) 517 | { 518 | recordfile = file; 519 | stopRecord = false; 520 | firstframe = false; 521 | xTaskCreatePinnedToCore(ArtnetESP32::recordArtnetTask, "recordArtnetTask", 3000, this, 1, &recordArtnetHandle, 0); 522 | } 523 | } 524 | 525 | void ArtnetESP32::stopArtnetRecord() 526 | { 527 | stopRecord = true; 528 | } 529 | 530 | void ArtnetESP32::recordArtnetTask(void *pvParameters) 531 | { 532 | ArtnetESP32 *artnet = (ArtnetESP32 *)pvParameters; 533 | artnet->nbframeread = 0; 534 | artnet->frameslues = 0; 535 | artnet->lostframes = 0; 536 | Serial.println("Ready to record"); 537 | while (!artnet->stopRecord) 538 | { 539 | if (artnet->nbframeread < artnet->frameslues) 540 | { 541 | if (artnet->frameslues == 1) 542 | { 543 | artnet->recordStartTime = millis(); 544 | Serial.println("First Frame recorded\n"); 545 | } 546 | artnet->getFrameForRecord(artnet->ledsbuffer); 547 | artnet->recordfile.write(artnet->ledsbuffer, artnet->nbPixels * 3); 548 | artnet->nbframeread++; 549 | if (artnet->frameRecordCallback) 550 | (*artnet->frameRecordCallback)(); 551 | } 552 | TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE; // write enable 553 | TIMERG0.wdt_feed = 1; // feed dog 554 | TIMERG0.wdt_wprotect = 0; 555 | } 556 | artnet->recordfile.close(); 557 | artnet->recordEndTime = millis(); 558 | Serial.printf("Record stopped framesartnet:%d framesrecorded:%d lostsframes:%d duration:%ld ms\n", artnet->frameslues, artnet->frameslues, artnet->lostframes, artnet->recordEndTime - artnet->recordStartTime); 559 | 560 | artnet->nbframeread = 0; 561 | artnet->frameslues = 0; 562 | artnet->lostframes = 0; 563 | vTaskDelete(NULL); 564 | } 565 | 566 | void ArtnetESP32::afterFrameTask(void *pvParameters) 567 | { 568 | ArtnetESP32 *artnet = (ArtnetESP32 *)pvParameters; 569 | for (;;) 570 | { 571 | 572 | ulTaskNotifyTake(pdTRUE, portMAX_DELAY); 573 | if (artnet->ledsbuffer != NULL) 574 | artnet->getBufferFrame(artnet->ledsbuffer); 575 | if (((ArtnetESP32 *)pvParameters)->frameCallback) 576 | (*((ArtnetESP32 *)pvParameters)->frameCallback)(); 577 | userArtnetHandle = 0; 578 | } 579 | } 580 | 581 | void ArtnetESP32::readFromSDTask(void *pvParameters) 582 | { 583 | ArtnetESP32 *artnet = (ArtnetESP32 *)pvParameters; 584 | for (;;) 585 | { 586 | 587 | ulTaskNotifyTake(pdTRUE, portMAX_DELAY); 588 | if (artnet->ledsbuffer != NULL) 589 | memcpy(artnet->ledsbuffer, artnet->getframeread(artnet->buffer_read) + 4, artnet->nbPixels * 3); 590 | if (((ArtnetESP32 *)pvParameters)->readFromSDCallback) 591 | (*((ArtnetESP32 *)pvParameters)->readFromSDCallback)(); 592 | } 593 | } 594 | 595 | uint16_t ArtnetESP32::read() 596 | { 597 | 598 | struct sockaddr_in si_other; 599 | int slen = sizeof(si_other), len; 600 | long timef = 0; 601 | //for(;;){ 602 | sync = 0; 603 | sync2 = 0; 604 | timef = millis(); 605 | while (sync != syncmax or sync2 != syncmax2) 606 | { 607 | if (millis() - timef > 1000) 608 | { 609 | Serial.println("Time out fired"); 610 | return 0; 611 | } 612 | 613 | //packetSize = Udp.parsePacket2(); 614 | 615 | /*char * buf = new char[1460]; 616 | if(!buf){ 617 | return 0; 618 | }*/ 619 | if ((len = recvfrom(Udp.udp_server, Udp.udpBuffer, 800, MSG_DONTWAIT, (struct sockaddr *)&si_other, (socklen_t *)&slen)) > 0) //1460 620 | 621 | { 622 | incomingUniverse = Udp.udpBuffer[14]; // | (Udp.udpBuffer[15] << 8);//artnetPacket[14] | (artnetPacket[15] << 8); 623 | timef = millis(); 624 | 625 | memcpy(&artnetleds1[nbPixelsPerUniverse * (incomingUniverse)*3], Udp.udpBuffer + ART_DMX_START, nbPixelsPerUniverse * 3); 626 | 627 | if (incomingUniverse == 0) 628 | { 629 | //Serial.println("*************new frame**************"); 630 | if (sync | sync2) 631 | { 632 | 633 | lostframes++; 634 | } 635 | sync = 1; 636 | sync2 = 0; 637 | } 638 | else 639 | { 640 | if (incomingUniverse < 32) 641 | sync = sync | (1 << incomingUniverse); 642 | else 643 | sync2 = sync2 | (1 << (incomingUniverse - 32)); 644 | } 645 | } 646 | } 647 | 648 | frameslues++; 649 | Udp.flush(); 650 | //currentframenumber=(currentframenumber+1)%2; 651 | // xTaskNotifyGive(task); 652 | //} 653 | return 1; 654 | } 655 | 656 | uint16_t ArtnetESP32::read(TaskHandle_t task) 657 | { 658 | 659 | struct sockaddr_in si_other; 660 | int slen = sizeof(si_other), len; 661 | long timef = 0; 662 | for (;;) 663 | { 664 | sync = 0; 665 | sync2 = 0; 666 | timef = millis(); 667 | while (sync != syncmax or sync2 != syncmax2) 668 | { 669 | if (millis() - timef > 1000) 670 | { 671 | Serial.println("Time out fired"); 672 | return 0; 673 | } 674 | 675 | //packetSize = Udp.parsePacket2(); 676 | 677 | /*char * buf = new char[1460]; 678 | if(!buf){ 679 | return 0; 680 | }*/ 681 | if ((len = recvfrom(Udp.udp_server, Udp.udpBuffer, 800, MSG_DONTWAIT, (struct sockaddr *)&si_other, (socklen_t *)&slen)) > 0) //1460 682 | 683 | { 684 | incomingUniverse = Udp.udpBuffer[14]; //| (Udp.udpBuffer[15] << 8);//artnetPacket[14] | (artnetPacket[15] << 8); 685 | timef = millis(); 686 | memcpy(&artnetleds1[nbPixelsPerUniverse * (incomingUniverse)*3 + currentframenumber * nbPixels * 3], Udp.udpBuffer + ART_DMX_START, nbPixelsPerUniverse * 3); 687 | 688 | if (incomingUniverse == 0) 689 | { 690 | //Serial.println("*************new frame**************"); 691 | if (sync | sync2) 692 | { 693 | 694 | lostframes++; 695 | } 696 | sync = 1; 697 | sync2 = 0; 698 | } 699 | else 700 | { 701 | if (incomingUniverse < 32) 702 | sync = sync | (1 << incomingUniverse); 703 | else 704 | sync2 = sync2 | (1 << (incomingUniverse - 32)); 705 | } 706 | } 707 | } 708 | 709 | frameslues++; 710 | Udp.flush(); 711 | //currentframenumber=(currentframenumber+1)%2; 712 | xTaskNotifyGive(task); 713 | } 714 | return 1; 715 | } 716 | 717 | void ArtnetESP32::printPacketHeader() 718 | { 719 | Serial.print("packet size = "); 720 | Serial.print(packetSize); 721 | Serial.print("\topcode = "); 722 | Serial.print(opcode, HEX); 723 | Serial.print("\tuniverse number = "); 724 | Serial.print(incomingUniverse); 725 | Serial.print("\tdata length = "); 726 | Serial.print(dmxDataLength); 727 | Serial.print("\tsequence n0. = "); 728 | Serial.println(sequence); 729 | } 730 | 731 | void ArtnetESP32::printPacketContent() 732 | { 733 | for (uint16_t i = ART_DMX_START; i < dmxDataLength; i++) 734 | { 735 | Serial.print(artnetPacket[i], DEC); 736 | Serial.print(" "); 737 | } 738 | Serial.println('\n'); 739 | } 740 | -------------------------------------------------------------------------------- /ArtnetESP32.h: -------------------------------------------------------------------------------- 1 | /*The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nathanaël Lécaudé 4 | https://github.com/natcl/Artnet, http://forum.pjrc.com/threads/24688-Artnet-to-OctoWS2811 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #ifndef ARTNETESP32_H 26 | #define ARTNETESP32_H 27 | //#define CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM 64 28 | #include 29 | //#include "FastLED.h" 30 | #if defined(ARDUINO_SAMD_ZERO) 31 | #include 32 | #elif defined(ESP8266) 33 | #include 34 | //#include "WifiUdp.h" 35 | #elif defined(ESP32) 36 | #include 37 | #include "UdpArtnet.h" 38 | //#include "WifiUdp.h" 39 | #else 40 | #include 41 | #include 42 | #endif 43 | #include 44 | // UDP specific 45 | 46 | #define ART_NET_PORT 6454 47 | // Opcodes 48 | #define ART_POLL 0x2000 49 | #define ART_POLL_REPLY 0x2100 50 | #define ART_DMX 0x5000 51 | #define ART_SYNC 0x5200 52 | // Buffers 53 | #define MAX_BUFFER_ARTNET 800 54 | // Packet 55 | #define ART_NET_ID "Art-Net\0" 56 | #define ART_DMX_START 18 57 | 58 | #include "freertos/queue.h" 59 | #include "soc/timer_group_struct.h" 60 | #include "soc/timer_group_reg.h" 61 | #include "esp_task_wdt.h" 62 | #include "FS.h" 63 | #include 64 | #include 65 | #define BUFFER_SIZE 800 66 | 67 | //static QueueHandle_t _artnet_queue; 68 | static TaskHandle_t recordArtnetHandle; 69 | static TaskHandle_t readFromSDHandle; 70 | static TaskHandle_t userArtnetHandle; 71 | 72 | struct artnet_reply_s 73 | { 74 | uint8_t id[8]; 75 | uint16_t opCode; 76 | uint8_t ip[4]; 77 | uint16_t port; 78 | uint8_t verH; 79 | uint8_t ver; 80 | uint8_t subH; 81 | uint8_t sub; 82 | uint8_t oemH; 83 | uint8_t oem; 84 | uint8_t ubea; 85 | uint8_t status; 86 | uint8_t etsaman[2]; 87 | uint8_t shortname[18]; 88 | uint8_t longname[64]; 89 | uint8_t nodereport[64]; 90 | uint8_t numbportsH; 91 | uint8_t numbports; 92 | uint8_t porttypes[4]; //max of 4 ports per node 93 | uint8_t goodinput[4]; 94 | uint8_t goodoutput[4]; 95 | uint8_t swin[4]; 96 | uint8_t swout[4]; 97 | uint8_t swvideo; 98 | uint8_t swmacro; 99 | uint8_t swremote; 100 | uint8_t sp1; 101 | uint8_t sp2; 102 | uint8_t sp3; 103 | uint8_t style; 104 | uint8_t mac[6]; 105 | uint8_t bindip[4]; 106 | uint8_t bindindex; 107 | uint8_t status2; 108 | uint8_t filler[26]; 109 | } __attribute__((packed)); 110 | 111 | class ArtnetESP32 112 | { 113 | public: 114 | ArtnetESP32(); 115 | TaskHandle_t artnetAfterFrameHandle; 116 | 117 | void (*frameCallback)(); 118 | void (*frameRecordCallback)(); 119 | void (*readFromSDCallback)(); 120 | static void afterFrameTask(void *pvParameters); 121 | static void recordArtnetTask(void *pvParameters); 122 | static void readFromSDTask(void *pvParameters); 123 | File recordfile; 124 | void startArtnetrecord(File file); 125 | bool stopRecord = true; 126 | void stopArtnetRecord(); 127 | // uint16_t readFrame(); 128 | uint16_t readFrameRecord(); 129 | uint16_t buffer_read = 0; 130 | uint16_t buffer_play = 0; 131 | uint32_t timenow = 0; 132 | uint8_t *getFrameReadSD(); 133 | uint32_t totalrecording = 0; 134 | // void getFrameandRecord(); 135 | uint8_t *ledsbuffer = NULL; 136 | void setLedsBuffer(uint8_t *leds); 137 | SemaphoreHandle_t Artnet_Semaphore2 = xSemaphoreCreateBinary(); 138 | uint16_t last_size = 0; 139 | uint32_t getsync(); 140 | uint32_t recordStartTime; 141 | uint32_t recordEndTime; 142 | int startuniverse; 143 | bool firstframe = false; 144 | bool readNextFrameAndWait(File playingfile); 145 | bool running = false; 146 | uint8_t *getframe(int framenumber); 147 | uint8_t *getframe(); 148 | //void getframe2(uint8_t* leds); 149 | void getframe(uint8_t *leds); 150 | void getBufferFrame(uint8_t *leds); 151 | void getFrameForRecord(uint8_t *leds); 152 | uint8_t *getframeread(int buffer); 153 | uint32_t nbframeread; 154 | uint32_t frameslues = 0; 155 | uint32_t lostframes = 0; 156 | void resetsync(); 157 | //void setframe(CRGB * frame); 158 | void begin(byte mac[], byte ip[]); 159 | void begin(); 160 | void begin(uint16_t nbPixels, uint16_t nbPixelsPerUniverses); 161 | void begin(uint16_t nbPixels, uint16_t nbPixelsPerUniverses, int starunivers); 162 | void setBroadcast(byte bc[]); 163 | uint16_t read(); 164 | uint16_t read2(); 165 | 166 | uint16_t readFrame() 167 | { 168 | uint16_t result; 169 | result = read3(); 170 | if (result == 1 and artnetAfterFrameHandle) 171 | xTaskNotifyGive(artnetAfterFrameHandle); 172 | return result; 173 | } 174 | uint16_t read3() 175 | { 176 | struct sockaddr_in si_other; 177 | int slen = sizeof(si_other), len; 178 | long timef = 0; 179 | 180 | //timef=millis(); 181 | incomingUniverse = 99; 182 | uint32_t decal = nbPixelsPerUniverse * 3 + ART_DMX_START; 183 | uint32_t decal2 = nbNeededUniverses * decal; 184 | uint8_t *offset, *offset2; 185 | // bool resetframe=true; 186 | offset2 = artnetleds1 + currentframenumber * decal2; 187 | er: 188 | 189 | #ifndef ARTNET_NO_TIMEOUT 190 | timef = millis(); 191 | #endif 192 | offset = offset2; 193 | 194 | while (incomingUniverse != startuniverse) 195 | { 196 | #ifndef ARTNET_NO_TIMEOUT 197 | if (millis() - timef > 1000) 198 | { 199 | Serial.println("Time out fired"); 200 | return 0; 201 | } 202 | #endif 203 | //MSG_DONTWAIT 204 | if ((len = recvfrom(Udp.udp_server, offset, BUFFER_SIZE, MSG_DONTWAIT, (struct sockaddr *)&si_other, (socklen_t *)&slen)) > 0) //1460 205 | { 206 | 207 | incomingUniverse = offset[14]; 208 | #ifdef ARTNET_DEBUG 209 | printf("Universe : %d length:%d \n", incomingUniverse, len); 210 | #endif 211 | } 212 | } 213 | #ifdef ARTNET_DEBUG 214 | printf("Passed\n"); 215 | #endif 216 | for (int uni = startuniverse + 1; uni < nbNeededUniverses + startuniverse; uni++) 217 | { 218 | offset += decal; 219 | //recvfrom(Udp.udp_server, offset, BUFFER_SIZE,MSG_DONTWAIT, (struct sockaddr *)&si_other, (socklen_t *)&slen) <= 0 220 | //recv(Udp.udp_server, offset, BUFFER_SIZE,MSG_DONTWAIT)<=0 221 | while ( recvfrom(Udp.udp_server, offset, BUFFER_SIZE,MSG_DONTWAIT, (struct sockaddr *)&si_other, (socklen_t *)&slen) <= 0) 222 | { 223 | //incomingUniverse = *(offset + 14); 224 | 225 | #ifndef ARTNET_NO_TIMEOUT 226 | if (millis() - timef > 1000) 227 | { 228 | Serial.println("Time out fired"); 229 | return 0; 230 | } 231 | 232 | #endif 233 | } 234 | incomingUniverse = *(offset + 14); 235 | if (incomingUniverse != uni) 236 | { 237 | #ifdef ARTNET_DEBUG 238 | printf("Universe : %d length:%d expected universer:%d\n", incomingUniverse, len, uni); 239 | #endif 240 | lostframes++; 241 | // resetframe=false; 242 | goto er; 243 | } 244 | } 245 | 246 | currentframenumber = (currentframenumber + 1) % 2; 247 | frameslues++; 248 | return 1; 249 | } 250 | 251 | uint16_t read4(); 252 | uint16_t readWithoutWaiting(); 253 | uint16_t read2(TaskHandle_t task); 254 | uint16_t read(TaskHandle_t task); 255 | void printPacketHeader(); 256 | void printPacketContent(); 257 | void stop(); 258 | 259 | uint32_t sync = 0; 260 | uint32_t syncmax = 0; 261 | uint32_t sync2 = 0; 262 | uint32_t syncmax2 = 0; 263 | uint32_t elaspe[2]; 264 | uint32_t start_time = 0; 265 | uint32_t current_time = 0; 266 | uint32_t getElaspseTime(); 267 | // Return a pointer to the start of the DMX data 268 | inline uint8_t *getDmxFrame(void) 269 | { 270 | return artnetPacket + ART_DMX_START; 271 | } 272 | 273 | inline uint16_t getOpcode(void) 274 | { 275 | return opcode; 276 | } 277 | 278 | inline uint8_t getSequence(void) 279 | { 280 | return sequence; 281 | } 282 | 283 | inline uint16_t getUniverse(void) 284 | { 285 | return incomingUniverse; 286 | } 287 | 288 | inline uint16_t getLength(void) 289 | { 290 | return dmxDataLength; 291 | } 292 | 293 | inline IPAddress getRemoteIP(void) 294 | { 295 | return remoteIP; 296 | } 297 | 298 | inline void setArtDmxCallback(void (*fptr)(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t *data, IPAddress remoteIP)) 299 | { 300 | artDmxCallback = fptr; 301 | } 302 | 303 | inline void setArtSyncCallback(void (*fptr)(IPAddress remoteIP)) 304 | { 305 | artSyncCallback = fptr; 306 | } 307 | 308 | inline void setFrameCallback(void (*fptr)()) 309 | { 310 | frameCallback = fptr; 311 | } 312 | 313 | inline void setframeRecordCallback(void (*fptr)()) 314 | { 315 | frameRecordCallback = fptr; 316 | } 317 | 318 | inline void setreadFromSDCallback(void (*fptr)()) 319 | { 320 | readFromSDCallback = fptr; 321 | } 322 | 323 | private: 324 | uint8_t node_ip_address[4]; 325 | 326 | uint8_t id[8]; 327 | #if defined(ARDUINO_SAMD_ZERO) || defined(ESP8266) || defined(ESP32) 328 | WiFiUDPArtnet Udp; 329 | // WiFiUDP Udp; 330 | #else 331 | EthernetUDP Udp; 332 | #endif 333 | struct artnet_reply_s ArtPollReply; 334 | 335 | uint16_t nbPixelsPerUniverse; 336 | uint16_t nbPixels; 337 | uint16_t nbNeededUniverses; 338 | uint8_t *artnetleds1; 339 | uint8_t *artnetleds2; 340 | uint8_t *artnetleds3; 341 | uint8_t *currentframe; 342 | uint8_t *frames[10]; 343 | uint8_t artnetPacket[MAX_BUFFER_ARTNET]; 344 | uint16_t packetSize; 345 | IPAddress broadcast; 346 | uint16_t opcode; 347 | uint8_t sequence; 348 | uint16_t incomingUniverse; 349 | uint16_t dmxDataLength; 350 | uint8_t currentframenumber; 351 | uint8_t buffernum; 352 | uint8_t readbuffer; 353 | IPAddress remoteIP; 354 | void (*artDmxCallback)(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t *data, IPAddress remoteIP); 355 | void (*artSyncCallback)(IPAddress remoteIP); 356 | }; 357 | 358 | #endif 359 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nathanaël Lécaudé 4 | https://github.com/natcl/Artnet, http://forum.pjrc.com/threads/24688-Artnet-to-OctoWS2811 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Artnet ESP32 library 2 | =========== 3 | 4 | This library is a take on the usage of the Artnet protocol to display leds and record on a SD using an esp32 when dealing with a large number of leds (> 800). This library will allow you to: 5 | * Live play of Artnet stream 6 | * Record the stream on a SD card 7 | * Replay the recorded file 8 | 9 | ## Couple of considerations when using Artnet 10 | 11 | ### Receiving Artnet universes 12 | The Artnet protocol is intensively used in light shows and numerous lighting software have the capability to output the data using the artnet protocol. The artnet protocol uses the concept of universe. A universe is a subset of the information you want to send. Each universe can contain 512 bytes of data + the header information. 13 | The Artnet protocol uses UDP internet protocol which does not provided receive/check/resend capabilities which means that when the artnet server sends out an artnet packet it doesn't wait for any acknowledgment before sending the next one. As a consequence a packet not intercepted by the artnet reciever is LOST. 14 | 15 | If you send RGB pixel informatiion you can have maximum 512/3 =170 pixels per universes. 16 | Hence if your project contains 1800 leds you would need at least 11 universes (10 full universes and the last one with 100 pixels). 17 | 18 | Now if you have an artnet broadcast at 25fps (each frame is 40ms appart) each universe will be maximum 4/11=0.36ms appart. 19 | Hence speed is critical in gathering the universes without losing any artnet packet. 20 | 21 | ### Using the universes 22 | For most users the use of artnet is the following : 23 | ```C 24 | Gather the artnet universes ->move the data in the leds buffer-> display the leds 25 | ^ | 26 | |___________________________________________________________________| 27 | ``` 28 | The WS281X are one of the most leds used. This led are cheap and easy to use. But they are clockless leds which means that the speed to 'upload' the data in them is fixed and in that case the 'upload speed' is 800kHz which means that to transfer the data for one led it requires 3x8bits/800.000=30us. Hence in our case 1800 x 30us=54ms. 29 | 30 | If using a sequentiel flow when displaying you will lose 2 artnet frames (the first one totally and part of the second frame => not usable) 31 | It is necessary to reduce the time needed to display the leds => split your leds over several PINS. If you're using the Fastled library it will divide the time by the number of pins used. Hence if you are using 4 pins (450 leds per pin) the display time will be 13.5ms hence losing only one frame. 32 | 33 | it will work but the display will not be smooth. 34 | 35 | ### Recording/Replaying the universes 36 | Writing/Reading to/from a SD card is also a time consuming process which could last longer than time betwwen two frames. The frames are not always sent with an exact timing depending on the artnet sending program. 37 | To be able to replay it with the most accurate timing the program will store in microseconds the delay between frames to be used during replay. 38 | 39 | ### Solutions implemented in this library 40 | 41 | #### Artnet universe processing speed 42 | To process the wifi packet and the artnet packet this library uses the barebone socket functions to reduce the processing. 43 | It gather all the universes at once removing all unnessary code out of this function 44 | 45 | #### Using the second core 46 | If we use a pure sequential workflow the program will not be listening to artnet packet while displaying the leds. Hence this library implements the use of the two cores of the ESP32 : one core is processing the data while the other one is displaying/recording the data. 47 | 48 | 49 | ### Take away 50 | When using artnet streaming, You need to take into account the timing issues : 51 | * Do not try to get artnet to broadcast at too much fps otherwise you will have too many lost frames in artnet 52 | * Dispatch your leds over several pins to lower the refresh time of your display 53 | * Do not hesitate to buy a good SD card to have a good write/read speed 54 | 55 | ## Library usage 56 | ### The artnet object 57 | ```C 58 | #include "ArnetESP32.h" 59 | ArtnetESP32 artnet; 60 | ``` 61 | ### Main functions 62 | #### `void begin(int NUM_LEDS,int UNIVERSE_SIZE)` 63 | This function starts the artnet objects. 64 | * `NUM_LEDS` is the total number of leds of your setup 65 | * `UNIVERSE_SIZE` is the size of you universes in pixels 66 | Based on these two numbers the program will calculate the number of needed universes 67 | 68 | #### `void setLedsBuffer(uint_8t*) buffer)` 69 | This function will set the buffer that will store the frame retrieved from `readFrame()` `readNextFrameAndWait()` and `readFrameRecord()` 70 | 71 | #### `uint16_t readFrame()` 72 | This function will receive one frame or all the needed universes. 73 | * Will exit with "Time out" after 1s of inactivity 74 | * Will return `1` if a frame has been received otherwise `0` when timeouting 75 | 76 | #### `void startArtnetrecord(File File)` 77 | This function sets up and starts what is needed to record the frames onto the file. 78 | 79 | #### `uint16_t readFrameRecord()` 80 | This function receives a frame a store it in the file. It also store the delay between frames 81 | * Will exit with "Time out" after 1s of inactivity 82 | * Will return `1` if a frame has been received otherwise `0` when timeouting 83 | 84 | #### `void stopArtnetRecord()` 85 | This function is mandatory to call to end the recording it will also close the file 86 | 87 | #### `bool readNextFrameAndWait(File file)` 88 | This function reads a frame from the file and waits the necessary time. 89 | it returns `true` if the file has not be entirely read else it will returns `false` 90 | 91 | 92 | ### The callback functions 93 | When uploading a frame, recording a frame or reading a frame from SD a callback function can be setup 94 | 95 | #### `void setFrameCallback(void (*fptr)())` 96 | This function set the function that will be called when a frame will be received using the `readFrame()` function 97 | 98 | #### `void setframeRecordCallback(void (*fptr)())` 99 | This function set the function that will be called when a frame will be received using the `readFrameRecord()` function 100 | 101 | #### `void setreadFromSDCallback(void (*fptr)())` 102 | This function set the function that will be called when a frame will be received using the `readNextFrameAndWait(File file)` function 103 | 104 | ## Examples 105 | Each example shows how to uses these functions : 106 | 107 | ### exampleArtnet 108 | This is the example to receive and display artnet 109 | 110 | ### SD-Arnet 111 | This is the example to record artnet 112 | 113 | ### readSD 114 | This is the example to replay the recorded artnet file 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /UdpArtnet.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Udp.cpp - UDP class for Raspberry Pi 3 | Copyright (c) 2016 Hristo Gochkov All right reserved. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | #include "UdpArtnet.h" 20 | #include 21 | #include 22 | #include 23 | 24 | #undef write 25 | #undef read 26 | 27 | WiFiUDPArtnet::WiFiUDPArtnet() 28 | : udp_server(-1) 29 | , server_port(0) 30 | , remote_port(0) 31 | , tx_buffer(0) 32 | , tx_buffer_len(0) 33 | , rx_buffer(0) 34 | {} 35 | 36 | WiFiUDPArtnet::~WiFiUDPArtnet(){ 37 | stop(); 38 | } 39 | 40 | uint8_t WiFiUDPArtnet::begin(IPAddress address, uint16_t port){ 41 | stop(); 42 | 43 | server_port = port; 44 | 45 | tx_buffer = new char[1460]; 46 | if(!tx_buffer){ 47 | log_e("could not create tx buffer: %d", errno); 48 | return 0; 49 | } 50 | 51 | if ((udp_server=socket(AF_INET, SOCK_DGRAM, 0)) == -1){ 52 | log_e("could not create socket: %d", errno); 53 | return 0; 54 | } 55 | 56 | int yes = 1; 57 | if (setsockopt(udp_server,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)) < 0) { 58 | log_e("could not set socket option: %d", errno); 59 | stop(); 60 | return 0; 61 | } 62 | 63 | struct sockaddr_in addr; 64 | memset((char *) &addr, 0, sizeof(addr)); 65 | addr.sin_family = AF_INET; 66 | addr.sin_port = htons(server_port); 67 | addr.sin_addr.s_addr = (in_addr_t)address; 68 | if(bind(udp_server , (struct sockaddr*)&addr, sizeof(addr)) == -1){ 69 | log_e("could not bind socket: %d", errno); 70 | stop(); 71 | return 0; 72 | } 73 | fcntl(udp_server, F_SETFL, O_NONBLOCK); 74 | return 1; 75 | } 76 | 77 | uint8_t WiFiUDPArtnet::begin(uint16_t p){ 78 | return begin(IPAddress(INADDR_ANY), p); 79 | } 80 | 81 | uint8_t WiFiUDPArtnet::beginMulticast(IPAddress a, uint16_t p){ 82 | if(begin(IPAddress(INADDR_ANY), p)){ 83 | if(a != 0){ 84 | struct ip_mreq mreq; 85 | mreq.imr_multiaddr.s_addr = (in_addr_t)a; 86 | mreq.imr_interface.s_addr = INADDR_ANY; 87 | if (setsockopt(udp_server, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { 88 | log_e("could not join igmp: %d", errno); 89 | stop(); 90 | return 0; 91 | } 92 | multicast_ip = a; 93 | } 94 | return 1; 95 | } 96 | return 0; 97 | } 98 | 99 | void WiFiUDPArtnet::stop(){ 100 | if(tx_buffer){ 101 | delete[] tx_buffer; 102 | tx_buffer = NULL; 103 | } 104 | tx_buffer_len = 0; 105 | if(rx_buffer){ 106 | cbuf *b = rx_buffer; 107 | rx_buffer = NULL; 108 | delete b; 109 | } 110 | if(udp_server == -1) 111 | return; 112 | if(multicast_ip != 0){ 113 | struct ip_mreq mreq; 114 | mreq.imr_multiaddr.s_addr = (in_addr_t)multicast_ip; 115 | mreq.imr_interface.s_addr = (in_addr_t)0; 116 | setsockopt(udp_server, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); 117 | multicast_ip = IPAddress(INADDR_ANY); 118 | } 119 | close(udp_server); 120 | udp_server = -1; 121 | } 122 | 123 | int WiFiUDPArtnet::beginMulticastPacket(){ 124 | if(!server_port || multicast_ip == IPAddress(INADDR_ANY)) 125 | return 0; 126 | remote_ip = multicast_ip; 127 | remote_port = server_port; 128 | return beginPacket(); 129 | } 130 | 131 | int WiFiUDPArtnet::beginPacket(){ 132 | if(!remote_port) 133 | return 0; 134 | 135 | // allocate tx_buffer if is necessary 136 | if(!tx_buffer){ 137 | tx_buffer = new char[1460]; 138 | if(!tx_buffer){ 139 | log_e("could not create tx buffer: %d", errno); 140 | return 0; 141 | } 142 | } 143 | 144 | tx_buffer_len = 0; 145 | 146 | // check whereas socket is already open 147 | if (udp_server != -1) 148 | return 1; 149 | 150 | if ((udp_server=socket(AF_INET, SOCK_DGRAM, 0)) == -1){ 151 | log_e("could not create socket: %d", errno); 152 | return 0; 153 | } 154 | 155 | fcntl(udp_server, F_SETFL, O_NONBLOCK); 156 | 157 | return 1; 158 | } 159 | 160 | int WiFiUDPArtnet::beginPacket(IPAddress ip, uint16_t port){ 161 | remote_ip = ip; 162 | remote_port = port; 163 | return beginPacket(); 164 | } 165 | 166 | int WiFiUDPArtnet::beginPacket(const char *host, uint16_t port){ 167 | struct hostent *server; 168 | server = gethostbyname(host); 169 | if (server == NULL){ 170 | log_e("could not get host from dns: %d", errno); 171 | return 0; 172 | } 173 | return beginPacket(IPAddress((const uint8_t *)(server->h_addr_list[0])), port); 174 | } 175 | 176 | int WiFiUDPArtnet::endPacket(){ 177 | struct sockaddr_in recipient; 178 | recipient.sin_addr.s_addr = (uint32_t)remote_ip; 179 | recipient.sin_family = AF_INET; 180 | recipient.sin_port = htons(remote_port); 181 | int sent = sendto(udp_server, tx_buffer, tx_buffer_len, 0, (struct sockaddr*) &recipient, sizeof(recipient)); 182 | if(sent < 0){ 183 | log_e("could not send data: %d", errno); 184 | return 0; 185 | } 186 | return 1; 187 | } 188 | 189 | size_t WiFiUDPArtnet::write(uint8_t data){ 190 | if(tx_buffer_len == 1460){ 191 | endPacket(); 192 | tx_buffer_len = 0; 193 | } 194 | tx_buffer[tx_buffer_len++] = data; 195 | return 1; 196 | } 197 | 198 | size_t WiFiUDPArtnet::write(const uint8_t *buffer, size_t size){ 199 | size_t i; 200 | for(i=0;i 0) { 225 | rx_buffer = new cbuf(len); 226 | rx_buffer->write(buf, len); 227 | } 228 | delete[] buf; 229 | return len; 230 | } 231 | 232 | int WiFiUDPArtnet::parsePacket2(){ 233 | //if(rx_buffer) 234 | // return 0; 235 | //Serial.println("dans la lig"); 236 | struct sockaddr_in si_other; 237 | int slen = sizeof(si_other) , len; 238 | /*char * buf = new char[1460]; 239 | if(!buf){ 240 | return 0; 241 | }*/ 242 | if ((len = recvfrom(udp_server, udpBuffer, 800, MSG_DONTWAIT, (struct sockaddr *) &si_other, (socklen_t *)&slen)) == -1) //1460 243 | { 244 | //delete[] buf; 245 | if(errno == EWOULDBLOCK){ 246 | return 0; 247 | } 248 | log_e("could not receive data: %d", errno); 249 | return 0; 250 | } 251 | //remote_ip = IPAddress(si_other.sin_addr.s_addr); 252 | //remote_port = ntohs(si_other.sin_port); 253 | /* if (len > 0) { 254 | // rx_buffer = new cbuf(len); 255 | //rx_buffer->write(buf, len); 256 | }*/ 257 | //delete[] buf; 258 | return len; 259 | } 260 | 261 | int WiFiUDPArtnet::available(){ 262 | if(!rx_buffer) return 0; 263 | return rx_buffer->available(); 264 | } 265 | 266 | int WiFiUDPArtnet::read(){ 267 | if(!rx_buffer) return -1; 268 | int out = rx_buffer->read(); 269 | if(!rx_buffer->available()){ 270 | cbuf *b = rx_buffer; 271 | rx_buffer = 0; 272 | delete b; 273 | } 274 | return out; 275 | } 276 | 277 | int WiFiUDPArtnet::read(unsigned char* buffer, size_t len){ 278 | return read((char *)buffer, len); 279 | } 280 | 281 | int WiFiUDPArtnet::read(char* buffer, size_t len){ 282 | if(!rx_buffer) return 0; 283 | int out = rx_buffer->read(buffer, len); 284 | if(!rx_buffer->available()){ 285 | cbuf *b = rx_buffer; 286 | rx_buffer = 0; 287 | delete b; 288 | } 289 | return out; 290 | } 291 | 292 | int WiFiUDPArtnet::peek(){ 293 | if(!rx_buffer) return -1; 294 | return rx_buffer->peek(); 295 | } 296 | 297 | void WiFiUDPArtnet::flush(){ 298 | if(!rx_buffer) return; 299 | cbuf *b = rx_buffer; 300 | rx_buffer = 0; 301 | delete b; 302 | } 303 | 304 | IPAddress WiFiUDPArtnet::remoteIP(){ 305 | return remote_ip; 306 | } 307 | 308 | uint16_t WiFiUDPArtnet::remotePort(){ 309 | return remote_port; 310 | } 311 | -------------------------------------------------------------------------------- /UdpArtnet.h: -------------------------------------------------------------------------------- 1 | // 2 | // UdpArtnet.h 3 | // 4 | // 5 | // Created by Yves BAZIN on 24/04/2020. 6 | // 7 | 8 | #ifndef UdpArtnet_h 9 | #define UdpArtnet_h 10 | 11 | 12 | /* 13 | * Udp.cpp: Library to send/receive UDP packets. 14 | * 15 | * NOTE: UDP is fast, but has some important limitations (thanks to Warren Gray for mentioning these) 16 | * 1) UDP does not guarantee the order in which assembled UDP packets are received. This 17 | * might not happen often in practice, but in larger network topologies, a UDP 18 | * packet can be received out of sequence. 19 | * 2) UDP does not guard against lost packets - so packets *can* disappear without the sender being 20 | * aware of it. Again, this may not be a concern in practice on small local networks. 21 | * For more information, see http://www.cafeaulait.org/course/week12/35.html 22 | * 23 | * MIT License: 24 | * Copyright (c) 2008 Bjoern Hartmann 25 | * Permission is hereby granted, free of charge, to any person obtaining a copy 26 | * of this software and associated documentation files (the "Software"), to deal 27 | * in the Software without restriction, including without limitation the rights 28 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 29 | * copies of the Software, and to permit persons to whom the Software is 30 | * furnished to do so, subject to the following conditions: 31 | * 32 | * The above copyright notice and this permission notice shall be included in 33 | * all copies or substantial portions of the Software. 34 | * 35 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 36 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 37 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 38 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 39 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 40 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 41 | * THE SOFTWARE. 42 | * 43 | * bjoern@cs.stanford.edu 12/30/2008 44 | */ 45 | 46 | 47 | 48 | #include 49 | #include 50 | #include 51 | 52 | class WiFiUDPArtnet: public UDP { 53 | 54 | public: 55 | 56 | WiFiUDPArtnet(); 57 | ~WiFiUDPArtnet(); 58 | int udp_server; 59 | uint8_t begin(IPAddress a, uint16_t p); 60 | uint8_t begin(uint16_t p); 61 | uint8_t beginMulticast(IPAddress a, uint16_t p); 62 | uint8_t udpBuffer[1460]; 63 | void stop(); 64 | int beginMulticastPacket(); 65 | int beginPacket(); 66 | int beginPacket(IPAddress ip, uint16_t port); 67 | int beginPacket(const char *host, uint16_t port); 68 | int endPacket(); 69 | size_t write(uint8_t); 70 | size_t write(const uint8_t *buffer, size_t size); 71 | int parsePacket(); 72 | int parsePacket2(); 73 | int available(); 74 | int read(); 75 | int read(unsigned char* buffer, size_t len); 76 | int read(char* buffer, size_t len); 77 | int peek(); 78 | void flush(); 79 | IPAddress remoteIP(); 80 | uint16_t remotePort(); 81 | 82 | private: 83 | //switch to public instead of private 84 | IPAddress multicast_ip; 85 | IPAddress remote_ip; 86 | uint16_t server_port; 87 | uint16_t remote_port; 88 | char * tx_buffer; 89 | size_t tx_buffer_len; 90 | cbuf * rx_buffer; 91 | }; 92 | 93 | 94 | 95 | 96 | #endif /* UdpArtnet_h */ 97 | -------------------------------------------------------------------------------- /examples/SD-Artnet/SD-Artnet.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "SD.h" 3 | #include "SPI.h" 4 | #include 5 | 6 | #include "FastLED.h" 7 | FASTLED_USING_NAMESPACE 8 | 9 | //The following has to be adapted to your specifications 10 | #define LED_WIDTH 123 11 | #define LED_HEIGHT 16 12 | #define NUM_LEDS LED_WIDTH*LED_HEIGHT 13 | #define UNIVERSE_SIZE 170 //my setup is 170 leds per universe no matter if the last universe is not full. 14 | CRGB leds[NUM_LEDS]; 15 | File myFile; 16 | 17 | ArtnetESP32 artnet; 18 | 19 | bool record_status=false; 20 | bool has_recorded=false; 21 | bool sd_card_present=false; 22 | const byte PIN_RECORD = 14; 23 | 24 | 25 | void recordfunction() 26 | { 27 | if (artnet.frameslues%100==0) 28 | Serial.printf("nb frames read: %d nb of incomplete frames:%d lost:%.2f %%\n",artnet.frameslues,artnet.lostframes,(float)(artnet.lostframes*100)/artnet.frameslues); 29 | //here the buffer is the led array hence a simple FastLED.show() is enough to display the array 30 | FastLED.show(); //if the array is really big I would not put any FastLED.show() because it takes time 31 | } 32 | 33 | 34 | void setup() { 35 | Serial.begin(115200); 36 | 37 | 38 | WiFi.mode(WIFI_STA); 39 | 40 | Serial.printf("Connecting "); 41 | WiFi.begin("", ""); 42 | 43 | while (WiFi.status() != WL_CONNECTED) { 44 | Serial.println(WiFi.status()); 45 | 46 | delay(500); 47 | Serial.print("."); 48 | } 49 | 50 | Serial.println(""); 51 | Serial.println("WiFi connected."); 52 | Serial.println("IP address: "); 53 | Serial.println(WiFi.localIP()); 54 | 55 | 56 | //set up your FastLED to your configuration ps: the more pins the better 57 | FastLED.addLeds(leds, NUM_LEDS); 58 | 59 | pinMode(PIN_RECORD,INPUT_PULLDOWN); 60 | 61 | if(!SD.begin()){ 62 | Serial.println("Card Mount Failed"); 63 | sd_card_present=false; 64 | } 65 | else 66 | { 67 | uint8_t cardType = SD.cardType(); 68 | if(cardType == CARD_NONE){ 69 | Serial.println("No SD card attached"); 70 | sd_card_present=false; 71 | } 72 | else 73 | { 74 | sd_card_present=true; 75 | } 76 | } 77 | myFile=SD.open("/filename",FILE_WRITE); 78 | artnet.setLedsBuffer((uint8_t*)leds); //set the buffer to put the frame once a frame has been received this is mandatory 79 | artnet.setframeRecordCallback(&recordfunction); //this is not mandatory 80 | if(sd_card_present) 81 | artnet.startArtnetrecord(myFile); 82 | artnet.begin(NUM_LEDS,UNIVERSE_SIZE); //configure artnet 83 | 84 | } 85 | 86 | void loop() { 87 | // put pin PIN_RECORD to HIGH to start the record 88 | // put it back to LOW to stop it 89 | while(record_status && sd_card_present){ 90 | 91 | has_recorded=true; 92 | artnet.readFrameRecord(); 93 | record_status=digitalRead(PIN_RECORD); 94 | } 95 | 96 | if(has_recorded && sd_card_present) 97 | { 98 | artnet.stopArtnetRecord(); 99 | } 100 | record_status=digitalRead(PIN_RECORD); 101 | } 102 | -------------------------------------------------------------------------------- /examples/exampleArtnet/exampleArtnet.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | 5 | #include "FastLED.h" 6 | FASTLED_USING_NAMESPACE 7 | 8 | //The following has to be adapted to your specifications 9 | #define LED_WIDTH 123 10 | #define LED_HEIGHT 48 11 | #define NUM_LEDS LED_WIDTH*LED_HEIGHT 12 | #define UNIVERSE_SIZE 170 //my setup is 170 leds per universe no matter if the last universe is not full. 13 | CRGB leds[NUM_LEDS]; 14 | 15 | 16 | ArtnetESP32 artnet; 17 | 18 | 19 | void displayfunction() 20 | { 21 | if (artnet.frameslues%100==0) 22 | Serial.printf("nb frames read: %d nb of incomplete frames:%d lost:%.2f %%\n",artnet.frameslues,artnet.lostframes,(float)(artnet.lostframes*100)/artnet.frameslues); 23 | //here the buffer is the led array hence a simple FastLED.show() is enough to display the array 24 | FastLED.show(); 25 | } 26 | 27 | 28 | void setup() { 29 | 30 | Serial.begin(115200); 31 | WiFi.mode(WIFI_STA); 32 | 33 | Serial.printf("Connecting "); 34 | WiFi.begin("", ""); 35 | 36 | while (WiFi.status() != WL_CONNECTED) { 37 | Serial.println(WiFi.status()); 38 | 39 | delay(500); 40 | Serial.print("."); 41 | } 42 | 43 | Serial.println(""); 44 | Serial.println("WiFi connected."); 45 | Serial.println("IP address: "); 46 | Serial.println(WiFi.localIP()); 47 | 48 | //set up your FastLED to your configuration ps: the more pins the better 49 | FastLED.addLeds(leds, NUM_LEDS); 50 | 51 | artnet.setFrameCallback(&displayfunction); //set the function that will be called back a frame has been received 52 | artnet.setLedsBuffer((uint8_t*)leds); //set the buffer to put the frame once a frame has been received 53 | 54 | artnet.begin(NUM_LEDS,UNIVERSE_SIZE); //configure artnet 55 | 56 | 57 | } 58 | 59 | void loop() { 60 | artnet.readFrame(); //ask to read a full frame 61 | } 62 | -------------------------------------------------------------------------------- /examples/readSD/readSD.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "SD.h" 3 | #include "SPI.h" 4 | #include 5 | 6 | #include "FastLED.h" 7 | FASTLED_USING_NAMESPACE 8 | 9 | //The following has to be adapted to your specifications 10 | #define LED_WIDTH 123 11 | #define LED_HEIGHT 16 12 | #define NUM_LEDS LED_WIDTH*LED_HEIGHT 13 | #define UNIVERSE_SIZE 170 //my setup is 170 leds per universe no matter if the last universe is not full. 14 | CRGB leds[NUM_LEDS]; 15 | File myFile; 16 | 17 | ArtnetESP32 artnet; 18 | 19 | uint32_t record_duration2=0; 20 | bool sd_card_present=false; 21 | 22 | void afterSDread() 23 | { 24 | FastLED.show(); 25 | } 26 | 27 | void setup() { 28 | Serial.begin(115200); 29 | 30 | 31 | WiFi.mode(WIFI_STA); 32 | 33 | Serial.printf("Connecting "); 34 | WiFi.begin("", ""); 35 | 36 | while (WiFi.status() != WL_CONNECTED) { 37 | Serial.println(WiFi.status()); 38 | 39 | delay(500); 40 | Serial.print("."); 41 | } 42 | 43 | Serial.println(""); 44 | Serial.println("WiFi connected."); 45 | Serial.println("IP address: "); 46 | Serial.println(WiFi.localIP()); 47 | 48 | 49 | //set up your FastLED to your configuration ps: the more pins the better 50 | FastLED.addLeds(leds, NUM_LEDS); 51 | 52 | 53 | 54 | if(!SD.begin()){ 55 | Serial.println("Card Mount Failed"); 56 | sd_card_present=false; 57 | } 58 | else 59 | { 60 | uint8_t cardType = SD.cardType(); 61 | if(cardType == CARD_NONE){ 62 | Serial.println("No SD card attached"); 63 | sd_card_present=false; 64 | } 65 | else 66 | { 67 | sd_card_present=true; 68 | } 69 | } 70 | myFile=SD.open("/filename"); 71 | artnet.setLedsBuffer((uint8_t*)leds); //set the buffer to put the frame once a frame has been received this is mandatory 72 | artnet.setreadFromSDCallback(&afterSDread); 73 | artnet.begin(NUM_LEDS,UNIVERSE_SIZE); //configure artnet 74 | 75 | } 76 | 77 | void loop() { 78 | if(sd_card_present) 79 | { 80 | if (!artnet.readNextFrameAndWait(myFile)) 81 | 82 | { 83 | record_duration2=millis()-record_duration2; 84 | myFile.seek(0); 85 | 86 | Serial.printf("duration %ld \n",record_duration2); 87 | 88 | record_duration2=millis(); 89 | 90 | } 91 | } 92 | } 93 | --------------------------------------------------------------------------------