// // wirelessly connected cloud or crowd // over WIFI over Internet over Mountains // across Rivers and the Universe // // // tailored for "Saekki-Chigi4" // 2026 04 13 - Seoul // // // NOTES // // taskscheduler 기반으로 cnmat/osc/udp로 esp8266에서 led를 on/off 하는 sub 장치를 만드는데.. // taskscheduler 우선 필요없지만.. ? 그렇네.. + wifi manager를 넣어주고.. // arduino-ota도 기본으로 넣어주고.. // taskscheduler는.. 그러니까... 모터 움직임 패턴을 읽어서.. 그대로 모터를 한번 돌려주는 걸로하고... // 읽는 시간 rate 일정한 값 + 패턴 값 저장하는 어레이 2개.. 모터 2개 드라이브. roller2.. // 1 taskschduler + array + roller2 기반.. reset되면 몇 초 후에.. 패턴 1을 재생하도록. -> DONE // 2 여기에 osc_udp 추가해서.. 외부에서, 패턴을 실행시킬 수 있도록. (pd로 osc로 전송) -> DONE // 3 여기에 wifimanager를 더해서.. 외부에서, 와이파이 설정을 할 수 있게 한다. // ==> 이것 자체는 어렵지 않아. 지금도 되고. // ==> 패턴을 보낼 수 있으면 좋겠는데.. // 1) 라이브로 패턴을 보낸다. 이 패턴은 바로 어레이(벡터)에 담고, 즉시 q를 갈 수 있게 준비가 된다. // 2) 라이브로 패턴 번호를 고른다. 이때, 1에서 보낸 패턴이.. 저장이 안되어있다면.. 그것은 날아간다? 아니면? // 1과2가 공존하는 시스템..? // 그럼 이렇게 해보지.. 우선 라이브로 보내는 패턴은 언제나 (-1)번에 저장됨. // 나중에 저장을 한다는 것은 이 (-1)의 패턴을 0,1,2,3... 중에 하나를 골라서 저장한다는 의미임. // 혹은 저장 전에 -1을 플레이 시키면 우선 들어보는 것도 물론 가능함. // 도중에 다른 번호를 불러서 들어본다고 -1의 패턴정보가 날아가는 것도 아님. // 그리고, 마찬가지로... 어떤 패턴을 피디의 어레이로 가져오는 것도 필요. reload. 이때도 번호를 주면서 reload 함. // 그리고 수정하고, 다시 저장 가능.. // 이렇게 생각해보면, 우선은.. 파라미터가 array 보다는 dictionary에 더 적합하게 아닌가 하는 생각도 들게 됨. // key, value 구조. // 그렇게 생각이 되면, 이걸 저장하는 구조도 일종의 json 등을 데이터 오브젝트, 딕셔너리를 바로 저장하는 방식이 아닐까 생각하게 되고, // 그러면 자연스럽게 json이 떠오름! // 마지막은.. 이 json을 어떻게 저장하거나 업데이트할것인가 하는것인데... // 그럼 개별 패턴을 개별 '파일'로 저장할 수 있을것인가? ... 그러는 편이 편리할까. 아니면..? ... // 만약 이런 구조가 된다면, 사실상 -1을 라이브 셋이라고 했던 부분은 더이상 '특수상황'이 아니라 // 그냥 general 한 모든 허용된 상황중에서 한가지 특수한 값을 지정해서 사용하고 있는 것에 지나지 않게 되어 바람직하다는 느낌이다. // 그럼, 여기서 이슈는 // 1 osc로 어레이 패턴을 보내고 받고 reload revise... 통신하는 법. // 2 이것을 저장하고 다시 불러오는 구조 // 우선은 ArduinoJson을 활용하면 1과 2에 상당한 문제들이 해결이 되는데. 2를 저장할때 littlefs를 쓸지, spiffs를 쓸지는 선택의 몫이다. // -------- // // (a quick save example) // #include // #include // // void saveConfig() { // // 1. Create a JSON document // JsonDocument doc; // doc["ssid"] = "MyWiFi"; // doc["password"] = "12345678"; // // // 2. Open file for writing // File configFile = LittleFS.open("/config.json", "w"); // if (!configFile) { // Serial.println("Failed to open config file for writing"); // return; // } // // // 3. Serialize JSON to file // serializeJson(doc, configFile); // configFile.close(); // } // // ... 도 하고, 상태 업데이트 할 수 있도록. (근데 이걸 위해서, 특수한 버튼 눌러야 한다면 flash버튼을 이용해 볼 수도 있겠다. pin0) // 4 이 모듈이 접속할 웹 공간 준비. 도메인 + 서비스. 그곳에 world energy 패치 준비.. (pd 여도 좋음.) - 아니면, 웹페이지여도 좋음. / 여기서 WE가 취합되고, 평균되고, 발표됨. 각 모듈은 이 값을 읽어감. (여기까지가 기존의 셋업 -> 인터넷으로 옮겨간 상태임.) // // -- 우선은 여기까지 하면, 갈수 있을지도 -- // // 5 pd에서 패턴값과.. 기본 파라미터를 조정하는 메세지 주기.. (+ 패턴값 체크하는 기능도 있으면 좋음...) // // ** IDENTITY ** #define MY_SPECIES ("YELLOW") #define MY_NAME ("YELOWEE {a.k.a. yellow5}") //arduino #include //wifi manager #include //osc over UDP #include #include #include #include #include // WiFiUDP Udp; // const IPAddress outIp(192,168,199,179); // server ip const unsigned int outPort = 9999; // server port const unsigned int localPort = 8888; // my port opened (<== expecting connection) OSCErrorCode error; //task #include Scheduler runner; //-*-*-*-*-*-*-*-*-*-*-*-*- // servo #define MOTOR_1A (D6) #define MOTOR_1B (D5) int speed = 0; bool isactive = false; void set_speed() { int r = speed; // if (r >= 0) { digitalWrite(MOTOR_1A, LOW); analogWrite(MOTOR_1B, r); } else { digitalWrite(MOTOR_1B, LOW); analogWrite(MOTOR_1A, r*(-1)); } // Serial.print("set_speed:"); // Serial.println(r); isactive = true; } Task set_speed_task(0, TASK_ONCE, &set_speed, &runner, false); // void rest() { analogWrite(MOTOR_1A, LOW); analogWrite(MOTOR_1B, LOW); isactive = false; } Task rest_task(0, TASK_ONCE, &rest, &runner, false); // #define MOTOR_2A (D3) #define MOTOR_2B (D2) int speed2 = 0; void set_speed2() { int r = speed2; // if (r >= 0) { digitalWrite(MOTOR_2A, LOW); analogWrite(MOTOR_2B, r); } else { digitalWrite(MOTOR_2B, LOW); analogWrite(MOTOR_2A, r*(-1)); } // Serial.print("set_speed2:"); // Serial.println(r); } Task set_speed2_task(0, TASK_ONCE, &set_speed2, &runner, false); // void rest2() { analogWrite(MOTOR_2A, LOW); analogWrite(MOTOR_2B, LOW); } Task rest2_task(0, TASK_ONCE, &rest2, &runner, false); //*-*-*-*-*-*-*-*-*-*-*-*-* //*-*-*-*-*-*-*-*-*-*-*-*-* //expressions //riff_A std::vector< std::vector > yellowA = { {0.266667,0.285714,0.295238,0.32381,0.352381,0.371429,0.409525,0.2,0.228572,0.257143,0.276191,0.285715,0.314286,0.32381,0.352382,0.37143,0.390477,0.238095,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.171429,0.209524,0.257143,0.276191,0.304763,0.333334,0.342858,-0.00952393,-0.00952393,-0.00952393,-0.00952393,-0.00952393,-0.00952393,-0.00952393,-0.00952393,-0.00952393,-0.00952393,-0.00952393}, //50 {0.704762,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.733333,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.704762,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.695238,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.714286,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.0190475,0.704762,0.0190475}, //50 {-0.00952327,0.771429,-0.00952327,-0.00952327,-0.00952327,-0.00952327,-0.00952327,-0.00952327,-0.00952327,-0.00952327,-0.00952327,-0.00952327,-0.00952327,0.771429,-0.00952327,-0.00952327,-0.00952327,1.78814e-07,1.78814e-07,1.78814e-07,1.78814e-07,1.78814e-07,1.78814e-07,1.78814e-07,1.78814e-07,1.78814e-07,0.780952,1.78814e-07,1.78814e-07,1.78814e-07,1.78814e-07,0,0,0,0,0,0,0,0,0,0,0,0,0.8,0,0,0,0,0,0,0,0,0,0,0,0,0,0.8,0,0,0,0,0,0,0,0,0,0,0,0.809524,0,0,0,0,0,0,0,0,0,0}, //80 {0.790476,-0.00952387,-0.00952387,0,0,0,0,0.780952,0,0,0,0,0,0,0,0.771429,0,0,0,0,0,0,0,0,0.790476,0,0,0,0,0} //30 }; // extern Task riff_A_task; int wordA = 0; float rateA = 20.0; // * 20 (scaling factor) // 94:b9:7e:?? ?? ?? => 100 // 94:b9:7e:14:4e:2c => 150 (?) // 94:b9:7e:14:49:b6 => 20 (x) // E8:DB:84:DF:40:C3 => 20 float intervalA = 500.0; //500 ms void riff_A() { //check out next letter! (==value) speed = yellowA[wordA][riff_A_task.getRunCounter()-1] * rateA; set_speed_task.restart(); //info Serial.print("riff_A (i="); Serial.print(riff_A_task.getRunCounter()-1); Serial.print(", r="); Serial.print(speed); Serial.println(")"); //if we are done... schedule a 'rest_task' if (riff_A_task.isLastIteration()) { rest_task.restartDelayed(intervalA + 100); } } Task riff_A_task(0, TASK_ONCE, &riff_A, &runner, false); // void riff_A_start() { riff_A_task.setInterval(intervalA); riff_A_task.setIterations(yellowA[wordA].size()); riff_A_task.restart(); // Serial.print("riff_A_start with wordA #"); Serial.println(wordA); } Task riff_A_start_task(0, TASK_ONCE, &riff_A_start, &runner, false); // //riff_B std::vector< std::vector > yellowB = { {0.685715,0.723811,0.800002,0.485714,0.519047,0.552381,0.580953,0,0,0,0,0,0,0,0,-0.0285652,-0.257127,-0.29522,-0.333314,-0.361884,-0.399977,-0.419024,-0.428548,4.17233e-07,0.00952381,0.00952381,0.00952381,0.00952381,0.00952381,0.00952381,0.00952381,0.0666683,0.00952798,-0.0571358,-0.276174,-0.285697,-0.304744,-0.314267,0.961907,0,0,0,0,0,0,0.438095,0.457142,0.457142,0,0.361904}, //50 {0,0.742857,0.00952387,0.00952387,0.00952387,0.00952387,0.742857,0.00952387,0.00952387,0.00952387,0.00952387,0.00952387,0.00952387,0.742857,0.00952387,0.00952387,0.00952387,0.00952387,0.00952387,0.00952387,0.742857,0.00952387,0.00952387,0.00952387,0.00952387,0.00952387,0.00952387,0.742857,0.00952387,0.00952387,0.00952387,0.742857,0.00952387,0.00952387,0.00952387,0.00952387,0.00952387,0.742857,0.00952387,0.00952387,0.00952387,0.00952387,0.714286,0.00952387,0.00952387,0.00952387,0.00952387,0.00952387,0.00952387,-5.96046e-08}, //50 {0,0.761905,0.685716,0.657145,0.504762,0.419048,0.390478,0,0,0,0,0,0,0,0,0,0.647619,0.8,0.609524,0.52381,0.428574,0,0,0,0,0,0,0,0,0,0.790476,0.685715,0.504764,0.476194,0.409528,0,0,0,0,0,0,0,0,0,0,0.8,0.647621,0.561909,0,0,0,0,0,0,0,0,0,0,0,0,0.847619,0.780953,0.704764,0.638098,0.552386,0.523815,0,0,0,0,0,0,0,0,0.866667,0.704763,0.619051,0.600003,0,0}, //80 {0.819048,0.819048,0.819048,-0.323816,-0.323816,0.819048,0.819048,0.819048,0.819048,0.828571,0.828571,0.828571,0.838095,-0.361912,-0.361912,0.847619,0.857143,0.857143,0.857143,0.857143,0.866666,0.866666,0.866666,-0.428579,-0.428579,0.866666,0.857143,0.847619,0.838095,0.809524} //30 }; // extern Task riff_B_task; int wordB = 0; float rateB = 10.0; // * 10 (scaling factor) // 94:b9:7e:?? ?? ?? => 100 // 94:b9:7e:14:4e:2c => 150 (?) // 94:b9:7e:14:49:b6 => 10 (x) // E8:DB:84:DF:40:C3 => 10 float intervalB = 500.0; //500 ms void riff_B() { //check out next letter! speed2 = yellowB[wordB][riff_B_task.getRunCounter()-1] * rateB; set_speed2_task.restart(); //info Serial.print("riff_B (i="); Serial.print(riff_B_task.getRunCounter()-1); Serial.print(", r="); Serial.print(speed2); Serial.println(")"); //if we are done... schedule a 'rest_task' if (riff_B_task.isLastIteration()) { rest2_task.restartDelayed(intervalB + 100); } } Task riff_B_task(0, TASK_ONCE, &riff_B, &runner, false); // void riff_B_start() { riff_B_task.setInterval(intervalB); riff_B_task.setIterations(yellowB[wordB].size()); riff_B_task.restart(); // Serial.print("riff_B_start with wordB #"); Serial.println(wordB); } Task riff_B_start_task(0, TASK_ONCE, &riff_B_start, &runner, false); //*-*-*-*-*-*-*-*-*-*-*-*-* // //*-*-*-*-*-*-*-*-*-*-*-*-* // // conductor for today (temporary conductor until wifi enabled.) // // // extern Task conductor_lonely_task; // float lonelytime = 5000; //ms // void conductor_lonely() { // // // static int count = 0; // if (count == yellowA.size()) count = 0; // // // if (isactive == false) { // if no more movement, then start another lonely song. // // // isactive = true; // // // lonelytime = random(3, 10) * 10000.0; //ms // // // wordA = count; // riff_A_start_task.restartDelayed(lonelytime); // wordB = count; // riff_B_start_task.restartDelayed(lonelytime); // // // Serial.print("conductor_lonely with lonelytime => "); // Serial.println(lonelytime); // Serial.print("conductor_lonely with count #"); // Serial.println(count); // // // count++; // } // } // Task conductor_lonely_task(1000, TASK_FOREVER, &conductor_lonely, &runner, false); // //*-*-*-*-*-*-*-*-*-*-*-*-* // ==[DISABLE]== // //task #0 : blink led // #define LED_PERIOD (11111) // #define LED_ONTIME (1) // #define LED_GAPTIME (222) // #define LED_PIN 2 // extern Task blink_task; // void blink() { // // // static int count = 0; // count++; // // // switch (count % 4) { // case 0: // digitalWrite(LED_PIN, LOW); // first ON // blink_task.delay(LED_ONTIME); // break; // case 1: // digitalWrite(LED_PIN, HIGH); // first OFF // blink_task.delay(LED_GAPTIME); // break; // case 2: // digitalWrite(LED_PIN, LOW); // second ON // blink_task.delay(LED_ONTIME); // break; // case 3: // digitalWrite(LED_PIN, HIGH); // second OFF // blink_task.delay(LED_PERIOD - 2* LED_ONTIME - LED_GAPTIME); // break; // } // } // Task blink_task(0, TASK_FOREVER, &blink, &runner, true); // -> ENABLED, at start-up. //task #1 : osc processing void route_note(OSCMessage& msg, int offset) { if (msg.fullMatch("/word", offset)) { wordA = msg.getFloat(0); wordB = msg.getFloat(0); riff_A_start_task.restartDelayed(100); riff_B_start_task.restartDelayed(100); // Serial.print("OSC: /word : "); Serial.print(wordA); Serial.print(", "); Serial.println(wordB); } } extern Task osc_task; void osc() { //osc OSCMessage msg; int size = Udp.parsePacket(); if (size > 0) { while (size--) { msg.fill(Udp.read()); } if(!msg.hasError()) { // on '/yellow' msg.route("/yellow", route_note); Serial.println("OSC: /yellow"); } } } Task osc_task(0, TASK_FOREVER, &osc, &runner, true); // -> ENABLED, at start-up. // void setup() { //led // pinMode(LED_PIN, OUTPUT); //pwm freq. analogWriteFreq(40000); //serial Serial.begin(115200); delay(100); //info Serial.println(); Serial.println(); Serial.println("\"hi, i m your friend.\""); Serial.println("-"); Serial.println("- my species: \"" + String(MY_SPECIES) + "\""); Serial.println("- call me ==> \"" + String(MY_NAME) + "\""); Serial.println("- mac address: " + WiFi.macAddress()); Serial.println("-"); //wifi-manager Serial.println("- calling.. wifimanager"); WiFiManager wm; if (!wm.autoConnect()) ESP.restart(); Serial.println("- i m connected!"); //open up udp connection Serial.println("- IP address: "); Serial.println(WiFi.localIP()); Serial.println("- Starting UDP"); Udp.begin(localPort); Serial.print("- Local port: "); Serial.println(Udp.localPort()); //all done. Serial.println("-"); Serial.println("\".-.-.-. :)\""); Serial.println(); //random seed randomSeed(analogRead(0)); //tasks // rest_task.restartDelayed(500); // rest2_task.restartDelayed(500); // // wordA = 0; // riff_A_start_task.restartDelayed(1000); // wordB = 0; // riff_B_start_task.restartDelayed(1000); // // conductor_lonely_task.restartDelayed(5000); // // for (int idx = 0; idx < yellowA.size(); idx++) Serial.println(yellowA[idx].size()); // for (int idx = 0; idx < yellowB.size(); idx++) Serial.println(yellowB[idx].size()); // } void loop() { // runner.execute(); // }