diff --git a/ATEM.cpp b/ATEM.cpp index c70be03..113753d 100755 --- a/ATEM.cpp +++ b/ATEM.cpp @@ -26,6 +26,10 @@ with the ATEM library. If not, see http://www.gnu.org/licenses/. #include "ATEM.h" + +/** + * Constructor, setting up IP address for the switcher (and local port to send packets from) + */ ATEM::ATEM(IPAddress ip, uint16_t localPort){ // Set up Udp communication object: EthernetUDP Udp; @@ -37,6 +41,9 @@ ATEM::ATEM(IPAddress ip, uint16_t localPort){ _serialOutput = false; } +/** + * Initiating connection handshake to the ATEM switcher + */ void ATEM::connect() { _localPacketIdCounter = 1; // Init localPacketIDCounter to 1; @@ -73,6 +80,7 @@ void ATEM::connect() { // Send connectAnswerString to ATEM: _Udp.beginPacket(_switcherIP, 9910); + // TODO: Describe packet contents according to rev.eng. API byte connectHelloAnswerString[] = { 0x80, 0x0c, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00 }; @@ -80,6 +88,11 @@ void ATEM::connect() { _Udp.endPacket(); } +/** + * Keeps connection to the switcher alive - basically, this means answering back to ping packages. + * Therefore: Call this in the Arduino loop() function and make sure it gets call at least 2 times a second + * Other recommendations might come up in the future. + */ void ATEM::runLoop() { // WARNING: @@ -108,11 +121,11 @@ void ATEM::runLoop() { // Read out packet length (first word), remote packet ID number and "command": uint16_t packetLength = word(_packetBuffer[0] & B00000111, _packetBuffer[1]); - uint16_t remotePacketID = word(_packetBuffer[10],_packetBuffer[11]); + _lastRemotePacketID = word(_packetBuffer[10],_packetBuffer[11]); uint8_t command = _packetBuffer[0] & B11111000; boolean command_ACK = command & B00001000 ? true : false; // If true, ATEM expects an acknowledgement answer back! // The five bits in "command" (from LSB to MSB): - // 1 = ACK, "Please respond to this packet" (using the remotePacketID). Exception: The initial 10-20 kbytes of Switcher status + // 1 = ACK, "Please respond to this packet" (using the _lastRemotePacketID). Exception: The initial 10-20 kbytes of Switcher status // 2 = ?. Set during initialization? (first hand-shake packets contains that) // 3 = "This is a retransmission". You will see this bit set if the ATEM switcher did not get a timely response to a packet. // 4 = ? ("hello packet" according to "ratte", forum at atemuser.com) @@ -125,6 +138,7 @@ void ATEM::runLoop() { // Currently we don't know any other way to decide if an answer should be sent back... if(!_hasInitialized && packetSize == 12) { _hasInitialized = true; + if (_serialOutput) Serial.println("_hasInitialized=TRUE"); } if (packetLength > 12) { @@ -132,13 +146,19 @@ void ATEM::runLoop() { } // If we are initialized, lets answer back no matter what: - if (_hasInitialized && command_ACK) { + // TODO: "_hasInitialized && " should be inserted back before "command_ACK" but + // with Arduino 1.0 UDP library it has proven MORE likely that the initial + // connection is made if we ALWAYS answer the switcher back. + // Apparently the initial "chaos" of keeping up with the incoming data confuses + // the UDP library so that we might never get initialized - and thus never get connected + // So... for now this is how we do it: + if (command_ACK) { if (_serialOutput) { Serial.print("ACK, rpID: "); - Serial.println(remotePacketID, DEC); + Serial.println(_lastRemotePacketID, DEC); } - _sendAnswerPacket(remotePacketID); + _sendAnswerPacket(_lastRemotePacketID); } } else { @@ -157,12 +177,14 @@ void ATEM::runLoop() { } } -void ATEM::serialOutput(boolean serialOutput) { - _serialOutput = serialOutput; -} - +/** + * If a package longer than a normal acknowledgement is received from the ATEM Switcher we must read through the contents. + * Usually such a package contains updated state information about the mixer + * Selected information is extracted in this function and transferred to internal variables in this library. + */ void ATEM::_parsePacket(uint16_t packetLength) { - + uint8_t idx; // General reusable index usable for keyers, mediaplayer etc below. + // If packet is more than an ACK packet (= if its longer than 12 bytes header), lets parse it: uint16_t indexPointer = 12; while (indexPointer < packetLength) { @@ -180,17 +202,117 @@ void ATEM::_parsePacket(uint16_t packetLength) { char cmdStr[] = { _packetBuffer[-2+4], _packetBuffer[-2+5], _packetBuffer[-2+6], _packetBuffer[-2+7], '\0' }; - // Extract the specific information we like to know about in this implementation: + // Extract the specific state information we like to know about: if(strcmp(cmdStr, "PrgI") == 0) { // Program Bus status _ATEM_PrgI = _packetBuffer[-2+8+1]; if (_serialOutput) Serial.print("Program Bus: "); - if (_serialOutput) Serial.println(_packetBuffer[-2+8+1], DEC); - } + if (_serialOutput) Serial.println(_ATEM_PrgI, DEC); + } else if(strcmp(cmdStr, "PrvI") == 0) { // Preview Bus status _ATEM_PrvI = _packetBuffer[-2+8+1]; if (_serialOutput) Serial.print("Preview Bus: "); if (_serialOutput) Serial.println(_packetBuffer[-2+8+1], DEC); - } + } else + if(strcmp(cmdStr, "TlIn") == 0) { // Tally status for inputs 1-8 + // Inputs 1-8, bit 0 = Prg tally, bit 1 = Prv tally. Both can be set simultaneously. + _ATEM_TlIn[0] = _packetBuffer[-2+8+2]; + _ATEM_TlIn[1] = _packetBuffer[-2+8+3]; + _ATEM_TlIn[2] = _packetBuffer[-2+8+4]; + _ATEM_TlIn[3] = _packetBuffer[-2+8+5]; + _ATEM_TlIn[4] = _packetBuffer[-2+8+6]; + _ATEM_TlIn[5] = _packetBuffer[-2+8+7]; + _ATEM_TlIn[6] = _packetBuffer[-2+8+8]; + _ATEM_TlIn[7] = _packetBuffer[-2+8+9]; + + if (_serialOutput) Serial.println("Tally updated: "); + } else + if(strcmp(cmdStr, "Time") == 0) { // Time. What is this anyway? + } else + if(strcmp(cmdStr, "TrPr") == 0) { // Transition Preview + _ATEM_TrPr = _packetBuffer[-2+8+1] > 0 ? true : false; + if (_serialOutput) Serial.print("Transition Preview: "); + if (_serialOutput) Serial.println(_ATEM_TrPr, BIN); + } else + if(strcmp(cmdStr, "TrPs") == 0) { // Transition Position + _ATEM_TrPs_frameCount = _packetBuffer[-2+8+2]; // Frames count down + _ATEM_TrPs_position = _packetBuffer[-2+8+4]*256 + _packetBuffer[-2+8+5]; // Position 0-1000 + } else + if(strcmp(cmdStr, "TrSS") == 0) { // Transition Style and Keyer on next transition + _ATEM_TrSS_KeyersOnNextTransition = _packetBuffer[-2+8+2] & B11111; // Bit 0: Background; Bit 1-4: Key 1-4 + if (_serialOutput) Serial.print("Keyers on Next Transition: "); + if (_serialOutput) Serial.println(_ATEM_TrSS_KeyersOnNextTransition, BIN); + + _ATEM_TrSS_TransitionStyle = _packetBuffer[-2+8+1]; + if (_serialOutput) Serial.print("Transition Style: "); // 0=MIX, 1=DIP, 2=WIPE, 3=DVE, 4=STING + if (_serialOutput) Serial.println(_ATEM_TrSS_TransitionStyle, DEC); + } else + if(strcmp(cmdStr, "FtbS") == 0) { // Fade To Black State + _ATEM_FtbS_frameCount = _packetBuffer[-2+8+2]; // Frames count down + } else + if(strcmp(cmdStr, "DskS") == 0) { // Downstream Keyer state. Also contains information about the frame count in case of "Auto" + idx = _packetBuffer[-2+8+0]; + if (idx >=0 && idx <=1) { + _ATEM_DskOn[idx] = _packetBuffer[-2+8+1] > 0 ? true : false; + if (_serialOutput) Serial.print("Dsk Keyer "); + if (_serialOutput) Serial.print(idx+1); + if (_serialOutput) Serial.print(": "); + if (_serialOutput) Serial.println(_ATEM_DskOn[idx], BIN); + } + } else + if(strcmp(cmdStr, "DskP") == 0) { // Downstream Keyer Tie + idx = _packetBuffer[-2+8+0]; + if (idx >=0 && idx <=1) { + _ATEM_DskTie[idx] = _packetBuffer[-2+8+1] > 0 ? true : false; + if (_serialOutput) Serial.print("Dsk Keyer"); + if (_serialOutput) Serial.print(idx+1); + if (_serialOutput) Serial.print(" Tie: "); + if (_serialOutput) Serial.println(_ATEM_DskTie[idx], BIN); + } + } else + if(strcmp(cmdStr, "KeOn") == 0) { // Upstead Keyer on + idx = _packetBuffer[-2+8+1]; + if (idx >=0 && idx <=3) { + _ATEM_KeOn[idx] = _packetBuffer[-2+8+2] > 0 ? true : false; + if (_serialOutput) Serial.print("Upstream Keyer "); + if (_serialOutput) Serial.print(idx+1); + if (_serialOutput) Serial.print(": "); + if (_serialOutput) Serial.println(_ATEM_KeOn[idx], BIN); + } + } else + if(strcmp(cmdStr, "ColV") == 0) { // Color Generator Change + // Todo: Relatively easy: 8 bytes, first is the color generator, the last 6 is hsl words + } else + if(strcmp(cmdStr, "MPCE") == 0) { // Media Player Clip Enable + idx = _packetBuffer[-2+8+0]; + if (idx >=0 && idx <=1) { + _ATEM_MPType[idx] = _packetBuffer[-2+8+1]; + _ATEM_MPStill[idx] = _packetBuffer[-2+8+2]; + _ATEM_MPClip[idx] = _packetBuffer[-2+8+3]; + } + } else + if(strcmp(cmdStr, "AuxS") == 0) { // Aux Output Source + uint8_t auxInput = _packetBuffer[-2+8+0]; + if (auxInput >=0 && auxInput <=2) { + _ATEM_AuxS[auxInput] = _packetBuffer[-2+8+1]; + if (_serialOutput) Serial.print("Aux "); + if (_serialOutput) Serial.print(auxInput+1); + if (_serialOutput) Serial.print(" Output: "); + if (_serialOutput) Serial.println(_ATEM_AuxS[auxInput], DEC); + } + + } else + if (_hasInitialized){ // All the rest... + if (_serialOutput) { + Serial.print("???? Unknown token: "); + Serial.print(cmdStr); + Serial.print(" : "); + } + for(uint8_t a=(-2+8);a0 ? true : false; +} +boolean ATEM::getPreviewTally(uint8_t inputNumber) { + // TODO: Validate that input number exists on current model! 1-8 at the moment. + return (_ATEM_TlIn[inputNumber-1] & 2) >0 ? true : false; +} + + + + + + +/******************************** + * + * ATEM Switcher Change methods + * Asks the switcher to changes something + * + ********************************/ + + + +void ATEM::changeProgramInput(uint8_t inputNumber) { // TODO: Validate that input number exists on current model! // On ATEM 1M/E: Black (0), 1 (1), 2 (2), 3 (3), 4 (4), 5 (5), 6 (6), 7 (7), 8 (8), Bars (9), Color1 (10), Color 2 (11), Media 1 (12), Media 2 (14) uint8_t commandBytes[4] = {0, inputNumber, 0, 0}; _sendCommandPacket("CPgI", commandBytes, 4); } - - -uint8_t ATEM::getATEM_PrvI() { - return _ATEM_PrvI; -} -void ATEM::setATEM_PrvI(uint8_t inputNumber) { +void ATEM::changePreviewInput(uint8_t inputNumber) { // TODO: Validate that input number exists on current model! uint8_t commandBytes[4] = {0, inputNumber, 0, 0}; _sendCommandPacket("CPvI", commandBytes, 4); } - -void ATEM::send_cut() { +void ATEM::doCut() { uint8_t commandBytes[4] = {0, 0xef, 0xbf, 0x5f}; // I don't know what that actually means... _sendCommandPacket("DCut", commandBytes, 4); } +void ATEM::doAuto() { + uint8_t commandBytes[4] = {0, 0x32, 0x16, 0x02}; // I don't know what that actually means... + _sendCommandPacket("DAut", commandBytes, 4); +} +void ATEM::fadeToBlackActivate() { + uint8_t commandBytes[4] = {0x00, 0x02, 0x58, 0x99}; + _sendCommandPacket("FtbA", commandBytes, 4); // Reflected back from ATEM in "FtbS" +} +void ATEM::changeTransitionPosition(word value) { + if (value>0 && value<=1000) { + uint8_t commandBytes[4] = {0, 0xe4, value/256, value%256}; + _sendCommandPacket("CTPs", commandBytes, 4); // Change Transition Position (CTPs) + } +} +void ATEM::changeTransitionPositionDone() { // When the last value of the transition is sent (1000), send this one too (we are done, change tally lights and preview bus!) + uint8_t commandBytes[4] = {0, 0xf6, 0, 0}; // Done + _sendCommandPacket("CTPs", commandBytes, 4); // Change Transition Position (CTPs) +} +void ATEM::changeTransitionPreview(bool state) { + uint8_t commandBytes[4] = {0x00, state ? 0x01 : 0x00, 0x00, 0x00}; + _sendCommandPacket("CTPr", commandBytes, 4); // Reflected back from ATEM in "TrPr" +} +void ATEM::changeTransitionType(uint8_t type) { + if (type>=0 && type<=4) { // 0=MIX, 1=DIP, 2=WIPE, 3=DVE, 4=STING + uint8_t commandBytes[4] = {0x01, 0x00, type, 0x02}; + _sendCommandPacket("CTTp", commandBytes, 4); // Reflected back from ATEM in "TrSS" + } +} +void ATEM::changeUpstreamKeyOn(uint8_t keyer, bool state) { + if (keyer>=1 && keyer<=4) { // Todo: Should match available keyers depending on model? + uint8_t commandBytes[4] = {0x00, keyer-1, state ? 0x01 : 0x00, 0x90}; + _sendCommandPacket("CKOn", commandBytes, 4); // Reflected back from ATEM in "KeOn" + } +} +void ATEM::changeUpstreamKeyNextTransition(uint8_t keyer, bool state) { // Not supporting "Background" + if (keyer>=1 && keyer<=4) { // Todo: Should match available keyers depending on model? + uint8_t stateValue = _ATEM_TrSS_KeyersOnNextTransition; + if (state) { + stateValue = stateValue | (B10 << (keyer-1)); + } else { + stateValue = stateValue & (~(B10 << (keyer-1))); + } + // TODO: Requires internal storage of state here so we can preserve all other states when changing the one we want to change. + uint8_t commandBytes[4] = {0x02, 0x00, 0x6a, stateValue & B11111}; + _sendCommandPacket("CTTp", commandBytes, 4); // Reflected back from ATEM in "TrSS" + } +} +void ATEM::changeDownstreamKeyOn(uint8_t keyer, bool state) { + if (keyer>=1 && keyer<=2) { // Todo: Should match available keyers depending on model? + uint8_t commandBytes[4] = {keyer-1, state ? 0x01 : 0x00, 0xff, 0xff}; + _sendCommandPacket("CDsL", commandBytes, 4); // Reflected back from ATEM in "DskP" and "DskS" + } +} +void ATEM::changeDownstreamKeyTie(uint8_t keyer, bool state) { + if (keyer>=1 && keyer<=2) { // Todo: Should match available keyers depending on model? + uint8_t commandBytes[4] = {keyer-1, state ? 0x01 : 0x00, 0xff, 0xff}; + _sendCommandPacket("CDsT", commandBytes, 4); + } +} +void ATEM::doAutoDownstreamKeyer(uint8_t keyer) { + if (keyer>=1 && keyer<=2) { // Todo: Should match available keyers depending on model? + uint8_t commandBytes[4] = {keyer-1, 0x32, 0x16, 0x02}; // I don't know what that actually means... + _sendCommandPacket("DDsA", commandBytes, 4); + } +} +void ATEM::changeAuxState(uint8_t auxOutput, uint8_t inputNumber) { + // TODO: Validate that input number exists on current model! + // On ATEM 1M/E: Black (0), 1 (1), 2 (2), 3 (3), 4 (4), 5 (5), 6 (6), 7 (7), 8 (8), Bars (9), Color1 (10), Color 2 (11), Media 1 (12), Media 1 Key (13), Media 2 (14), Media 2 Key (15), Program (16), Preview (17), Clean1 (18), Clean 2 (19) + + if (auxOutput>=1 && auxOutput<=3) { // Todo: Should match available aux outputs + uint8_t commandBytes[4] = {auxOutput-1, inputNumber, 0, 0}; + _sendCommandPacket("CAuS", commandBytes, 4); + } +} +void ATEM::settingsMemorySave() { + uint8_t commandBytes[4] = {0, 0, 0, 0}; + _sendCommandPacket("SRsv", commandBytes, 4); +} +void ATEM::settingsMemoryClear() { + uint8_t commandBytes[4] = {0, 0, 0, 0}; + _sendCommandPacket("SRcl", commandBytes, 4); +} +void ATEM::changeColorValue(uint8_t colorGenerator, uint16_t hue, uint16_t saturation, uint16_t lightness) { + if (colorGenerator>=1 && colorGenerator<=2 + && hue>=0 && hue<=3600 + && saturation >=0 && saturation <=1000 + && lightness >=0 && lightness <= 1000 + ) { // Todo: Should match available aux outputs + uint8_t commandBytes[8] = {0x07, colorGenerator-1, + highByte(hue), lowByte(hue), + highByte(saturation), lowByte(saturation), + highByte(lightness), lowByte(lightness) + }; + _sendCommandPacket("CClV", commandBytes, 8); + } +} +void ATEM::mediaPlayerSelectSource(uint8_t mediaPlayer, boolean movieclip, uint8_t sourceIndex) { + if (mediaPlayer>=1 && mediaPlayer<=2) { // TODO: Adjust to particular ATEM model... (here 1M/E) + uint8_t commandBytes[12]; + memset(commandBytes, 0, 12); + commandBytes[1] = mediaPlayer-1; + if (movieclip) { + commandBytes[0] = 4; + if (sourceIndex>=1 && sourceIndex<=2) { + commandBytes[4] = sourceIndex-1; + } + } else { + commandBytes[0] = 2; + if (sourceIndex>=1 && sourceIndex<=32) { + commandBytes[3] = sourceIndex-1; + } + } + commandBytes[9] = 0x10; + _sendCommandPacket("MPSS", commandBytes, 12); + + // For some reason you have to send this command immediate after (or in fact it could be in the same packet) + // If not done, the clip will not change if there is a shift from stills to clips or vice versa. + uint8_t commandBytes2[8] = {0x01, mediaPlayer-1, movieclip?2:1, 0xbf, movieclip?0x96:0xd5, 0xb6, 0x04, 0}; + _sendCommandPacket("MPSS", commandBytes2, 8); + } +} \ No newline at end of file diff --git a/ATEM.h b/ATEM.h index 93c585b..704e2e7 100755 --- a/ATEM.h +++ b/ATEM.h @@ -40,40 +40,93 @@ with the ATEM library. If not, see http://www.gnu.org/licenses/. class ATEM { private: - EthernetUDP _Udp; // Udp Object for communication, see constructor. - uint16_t _localPort; // local port to send from - IPAddress _switcherIP; // IP address of the switcher - boolean _serialOutput; + EthernetUDP _Udp; // Udp Object for communication, see constructor. + uint16_t _localPort; // local port to send from + IPAddress _switcherIP; // IP address of the switcher + boolean _serialOutput; // If set, the library will print status/debug information to the Serial object - uint8_t _sessionID; // Used internally for storing packet size during communication - char _packetBuffer[96]; // Buffer for storing segments of the packets from ATEM. Has the size of the largest known "segment" of a packet the ATEM sends. + uint8_t _sessionID; // Used internally for storing packet size during communication + uint16_t _lastRemotePacketID; // The most recent Remote Packet Id from switcher + char _packetBuffer[96]; // Buffer for storing segments of the packets from ATEM. Has the size of the largest known "segment" of a packet the ATEM sends. - uint16_t _localPacketIdCounter; // This is our counter for the command packages we might like to send to ATEM. + uint16_t _localPacketIdCounter; // This is our counter for the command packages we might like to send to ATEM boolean _hasInitialized; // If true, the initial reception of the ATEM memory has passed and we can begin to respond during the runLoop() - uint8_t _answer[24]; // Little buffer for creating answers back to the ATEM - - // Selected ATEM Status values: - uint8_t _ATEM_PrgI; // Program input channel - uint8_t _ATEM_PrvI; // Preview input channel - + uint8_t _answer[36]; // Little buffer for creating answers back to the ATEM + + // Selected ATEM State values. Naming attempts to match the switchers own protocol names + // Set through _parsePacket() when the switcher sends state information + // Accessed through getter methods + uint8_t _ATEM_PrgI; // Program input + uint8_t _ATEM_PrvI; // Preview input + uint8_t _ATEM_TlIn[8]; // Inputs 1-8, bit 0 = Prg tally, bit 1 = Prv tally. Both can be set simultaneously. + boolean _ATEM_TrPr; // Transition Preview: Is it on or not? + uint8_t _ATEM_TrSS_KeyersOnNextTransition; // Bit 0: Background; Bit 1-4: Key 1-4 + uint8_t _ATEM_TrSS_TransitionStyle; // 0=MIX, 1=DIP, 2=WIPE, 3=DVE, 4=STING + boolean _ATEM_KeOn[4]; // Upstream Keyer 1-4 On state + boolean _ATEM_DskOn[2]; // Downstream Keyer 1-2 On state + boolean _ATEM_DskTie[2]; // Downstream Keyer Tie 1-2 On state + uint8_t _ATEM_TrPs_frameCount; // Count down of frames in case of a transition (manual or auto) + uint16_t _ATEM_TrPs_position; // Position from 0-1000 of the current transition in progress + uint8_t _ATEM_FtbS_frameCount; // Count down of frames in case of fade-to-black + uint8_t _ATEM_AuxS[3]; // Aux Outputs 1-3 source + uint8_t _ATEM_MPType[2]; // Media Player 1/2: Type (1=Clip, 2=Still) + uint8_t _ATEM_MPStill[2]; // Still number (if MPType==2) + uint8_t _ATEM_MPClip[2]; // Clip number (if MPType==1) + + public: ATEM(IPAddress ip, uint16_t localPort); void connect(); void runLoop(); - void serialOutput(boolean serialOutput); - private: - void _sendAnswerPacket(uint16_t remotePacketID); void _parsePacket(uint16_t packetLength); + void _sendAnswerPacket(uint16_t remotePacketID); void _sendCommandPacket(char cmd[4], uint8_t commandBytes[16], uint8_t cmdBytes); - + + public: - void setATEM_PrgI(uint8_t inputNumber); - uint8_t getATEM_PrgI(); - void setATEM_PrvI(uint8_t inputNumber); - uint8_t getATEM_PrvI(); - void send_cut(); + +/******************************** + * General Getter/Setter methods + ********************************/ + void serialOutput(boolean serialOutput); + bool hasInitialized(); + uint16_t getATEM_lastRemotePacketId(); + +/******************************** +* ATEM Switcher state methods +* Returns the most recent information we've +* got about the switchers state + ********************************/ + uint8_t getProgramInput(); + uint8_t getPreviewInput(); + boolean getProgramTally(uint8_t inputNumber); + boolean getPreviewTally(uint8_t inputNumber); + +/******************************** + * ATEM Switcher Change methods + * Asks the switcher to changes something + ********************************/ + void changeProgramInput(uint8_t inputNumber); + void changePreviewInput(uint8_t inputNumber); + void doCut(); + void doAuto(); + void fadeToBlackActivate(); + void changeTransitionPosition(word value); + void changeTransitionPositionDone(); + void changeTransitionPreview(bool state); + void changeTransitionType(uint8_t type); + void changeUpstreamKeyOn(uint8_t keyer, bool state); + void changeUpstreamKeyNextTransition(uint8_t keyer, bool state); + void changeDownstreamKeyOn(uint8_t keyer, bool state); + void changeDownstreamKeyTie(uint8_t keyer, bool state); + void doAutoDownstreamKeyer(uint8_t keyer); + void changeAuxState(uint8_t auxOutput, uint8_t inputNumber); + void settingsMemorySave(); + void settingsMemoryClear(); + void changeColorValue(uint8_t colorGenerator, uint16_t hue, uint16_t saturation, uint16_t lightness); + void mediaPlayerSelectSource(uint8_t mediaPlayer, boolean movieclip, uint8_t sourceIndex); }; #endif diff --git a/README b/README index 6f348b4..5ba03f6 100644 --- a/README +++ b/README @@ -4,7 +4,7 @@ Now, with this arduino library, you can develop your own custom hardware designs Tutorial: I made a short video tutorial. http://www.youtube.com/watch?v=zvbWMKGv4BQ -I'm assuming that you have an Arduino with Ethernet and probably also managed to upload the "blink" program to see it working. But thats all you need in addition to installing this library in the Documents/Arduino/Libraries/ folder on your computer. Restart Arduino and you will find two pieces of example code for "ATEM" in the menu. Watch the video for details. You will be well of if you have a breadboard to set up a few LEDs and switches to play with. +I'm assuming that you have an Arduino with Ethernet and probably also managed to upload the "blink" program to see it working. But thats all you need in addition to installing this library in the Documents/Arduino/Libraries/ folder on your computer. Restart Arduino and you will find two pieces of example code for "ATEM" in the menu. Watch the video for details. You will be well off if you have a breadboard to set up a few LEDs and switches to play with. GPL licensed: The library is licensed under GNU GPL v3. It allows you to use the library for any project, even commercial ones, as long as you keep the code using the library open - and deliver a copy to your client. In other words, even though you might deliver a black box hardware device, you still must give your client a copy of the Arduino sketch you have uploaded to the board. And how knows; either they will improve your product, maybe do nothing at all - or mess it up so you can sell some support hours. :-) diff --git a/TODO.txt b/TODO.txt index 6884d01..f1e5536 100644 --- a/TODO.txt +++ b/TODO.txt @@ -3,9 +3,11 @@ Todo list: Generally, search for "todo" in the source and there will pop up a few places where improvements are needed. Here is a more general list I'm keeping: -- Detect switcher type and firmware version in library -- In runloop, detect if connection is kept open (maybe mimick how the PC software is also sending keep-alive packets every half second), and if down, try to reconnect automatically. This is more nice-to-have - a reconnection can easily be established with a hardware reset of the Arduino. -- Implement key-on for upstream keyers (We know how to protocol wise...) +* Implement more palette features (like configuration of the keyers) +* Detect switcher type and firmware version in library +* In runloop, detect if connection is kept open (maybe mimick how the PC software is also sending keep-alive packets every half second), and if down, try to reconnect automatically. This is more nice-to-have - a reconnection can easily be established with a hardware reset of the Arduino. +* Look into a possible bug in the UDP library of Arduino - one that may cause buffer overflows and resulting non-connection to ATEM switcher + A note from the atemuser.com forum, which I might need to keep in mind although I haven't experienced the need for this: // You must listen for when the Atem starts to use an new 'UUID' or session ID, and start using that when sending commands. diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 0000000..c46425f --- /dev/null +++ b/changelog.txt @@ -0,0 +1,7 @@ + +23/2 Changes (By Kasper Skårhøj) + * Implemented all basic features found in the switcher interface. + * Implemented a few of the "palette" features, such as setting the color generators and selecting media for the media banks. + * Cleaned up method names in the library - you WILL need to change those names if you used the first beta version of the library + * There are now separate functions for Program and Preview tally - they should reflect how inputs are used in keyers and during transitions. + diff --git a/examples/ATEM1MEFunctionTest/ATEM1MEFunctionTest.pde b/examples/ATEM1MEFunctionTest/ATEM1MEFunctionTest.pde new file mode 100644 index 0000000..9041a4b --- /dev/null +++ b/examples/ATEM1MEFunctionTest/ATEM1MEFunctionTest.pde @@ -0,0 +1,353 @@ +/* +Copyright 2012 Kasper Skårhøj, SKAARHOJ, kasperskaarhoj@gmail.com + + This file is part of the ATEM library for Arduino + + The ATEM library is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The ATEM library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with the ATEM library. If not, see http://www.gnu.org/licenses/. + + */ + + + + +/***************** + * Example: ATEM Monitor + * Connects to the Atem Switcher and outputs changes to Preview and Program on the Serial monitor (at 9600 baud) + */ +/***************** + * TO MAKE THIS EXAMPLE WORK: + * - You must have an Arduino with Ethernet Shield (or compatible such as "Arduino Ethernet", http://arduino.cc/en/Main/ArduinoBoardEthernet) + * - You must have an Atem Switcher connected to the same network as the Arduino - and you should have it working with the desktop software + * - You must make specific set ups in the below lines where the comment "// SETUP" is found! + */ + + + + + +#include // needed for Arduino versions later than 0018 +#include + + +// MAC address and IP address for this *particular* Ethernet Shield! +// MAC address is printed on the shield +// IP address is an available address you choose on your subnet where the switcher is also present: +byte mac[] = { + 0x90, 0xA2, 0xDA, 0x00, 0xE8, 0xE9 }; // <= SETUP +IPAddress ip(192, 168, 0, 20); // <= SETUP + + +// Include ATEM library and make an instance: +#include + +// Connect to an ATEM switcher on this address and using this local port: +// The port number is chosen randomly among high numbers. +ATEM AtemSwitcher(IPAddress(192, 168, 0, 50), 56417); // <= SETUP (the IP address of the ATEM switcher) + + + +void setup() { + + // Start the Ethernet, Serial (debugging) and UDP: + Ethernet.begin(mac,ip); + Serial.begin(9600); + Serial.println("Serial started."); + + pinMode(7, INPUT); + + // Initialize a connection to the switcher: + AtemSwitcher.serialOutput(true); + AtemSwitcher.connect(); +} + +boolean buttonState = false; +int c = 0; + +void loop() { + // Check for packets, respond to them etc. Keeping the connection alive! + AtemSwitcher.runLoop(); + + // Detech press of a button on digital input 7: + if (digitalRead(7)) { + if (buttonState == false) { + c++; + + switch(c) { + // Program select + case 1: + AtemSwitcher.changeProgramInput(0); + break; + case 2: + AtemSwitcher.changeProgramInput(1); + break; + case 3: + AtemSwitcher.changeProgramInput(2); + break; + case 4: + AtemSwitcher.changeProgramInput(3); + break; + case 5: + AtemSwitcher.changeProgramInput(4); + break; + case 6: + AtemSwitcher.changeProgramInput(5); + break; + case 7: + AtemSwitcher.changeProgramInput(6); + break; + case 8: + AtemSwitcher.changeProgramInput(7); + break; + case 9: + AtemSwitcher.changeProgramInput(8); + break; + case 10: + AtemSwitcher.changeProgramInput(9); + break; + case 11: + AtemSwitcher.changeProgramInput(10); + break; + case 12: + AtemSwitcher.changeProgramInput(11); + break; + case 13: + AtemSwitcher.changeProgramInput(12); + break; + case 14: + AtemSwitcher.changeProgramInput(14); + break; + // Preview select: + case 15: + AtemSwitcher.changePreviewInput(0); + break; + case 16: + AtemSwitcher.changePreviewInput(1); + break; + case 17: + AtemSwitcher.changePreviewInput(2); + break; + case 18: + AtemSwitcher.changePreviewInput(3); + break; + case 19: + AtemSwitcher.changePreviewInput(4); + break; + case 20: + AtemSwitcher.changePreviewInput(5); + break; + case 21: + AtemSwitcher.changePreviewInput(6); + break; + case 22: + AtemSwitcher.changePreviewInput(7); + break; + case 23: + AtemSwitcher.changePreviewInput(8); + break; + case 24: + AtemSwitcher.changePreviewInput(9); + break; + case 25: + AtemSwitcher.changePreviewInput(10); + break; + case 26: + AtemSwitcher.changePreviewInput(11); + break; + case 27: + AtemSwitcher.changePreviewInput(12); + break; + case 28: + AtemSwitcher.changePreviewInput(14); + break; + // Cut / Transitions + case 29: + AtemSwitcher.changeProgramInput(3); + AtemSwitcher.changePreviewInput(4); + break; + case 30: + AtemSwitcher.doCut(); + break; + case 31: + AtemSwitcher.doAuto(); + break; + case 32: + AtemSwitcher.fadeToBlackActivate(); + break; + case 33: + AtemSwitcher.fadeToBlackActivate(); + break; + case 34: + AtemSwitcher.changeTransitionPosition(500); + break; + case 35: + AtemSwitcher.changeTransitionPosition(1000); + AtemSwitcher.changeTransitionPositionDone(); + break; + case 36: + AtemSwitcher.changeTransitionPreview(true); + break; + case 37: + AtemSwitcher.changeTransitionPreview(false); + break; + case 38: + AtemSwitcher.changeTransitionType(1); + break; + case 39: + AtemSwitcher.changeTransitionType(2); + break; + case 40: + AtemSwitcher.changeTransitionType(3); + break; + case 41: + AtemSwitcher.changeTransitionType(4); + break; + case 42: + AtemSwitcher.changeTransitionType(0); // Back to mix + break; + case 43: + AtemSwitcher.changeUpstreamKeyOn(1, true); + break; + case 44: + AtemSwitcher.changeUpstreamKeyOn(2, true); + break; + case 45: + AtemSwitcher.changeUpstreamKeyOn(3, true); + break; + case 46: + AtemSwitcher.changeUpstreamKeyOn(4, true); + break; + case 47: + AtemSwitcher.changeUpstreamKeyOn(1, false); + break; + case 48: + AtemSwitcher.changeUpstreamKeyOn(2, false); + break; + case 49: + AtemSwitcher.changeUpstreamKeyOn(3, false); + break; + case 50: + AtemSwitcher.changeUpstreamKeyOn(4, false); + break; + case 51: + AtemSwitcher.changeUpstreamKeyNextTransition(1, true); + break; + case 52: + AtemSwitcher.changeUpstreamKeyNextTransition(2, true); + break; + case 53: + AtemSwitcher.changeUpstreamKeyNextTransition(3, true); + break; + case 54: + AtemSwitcher.changeUpstreamKeyNextTransition(4, true); + break; + case 55: + AtemSwitcher.changeUpstreamKeyNextTransition(1, false); + break; + case 56: + AtemSwitcher.changeUpstreamKeyNextTransition(2, false); + break; + case 57: + AtemSwitcher.changeUpstreamKeyNextTransition(3, false); + break; + case 58: + AtemSwitcher.changeUpstreamKeyNextTransition(4, false); + break; + case 59: + AtemSwitcher.changeDownstreamKeyOn(1, true); + break; + case 60: + AtemSwitcher.changeDownstreamKeyOn(2, true); + break; + case 61: + AtemSwitcher.changeDownstreamKeyOn(1, false); + break; + case 62: + AtemSwitcher.changeDownstreamKeyOn(2, false); + break; + case 63: + AtemSwitcher.changeDownstreamKeyTie(1, true); + break; + case 64: + AtemSwitcher.changeDownstreamKeyTie(2, true); + break; + case 65: + AtemSwitcher.changeDownstreamKeyTie(1, false); + break; + case 66: + AtemSwitcher.changeDownstreamKeyTie(2, false); + break; + + case 67: + AtemSwitcher.doAutoDownstreamKeyer(1); + break; + case 68: + AtemSwitcher.doAutoDownstreamKeyer(2); + break; + case 69: + AtemSwitcher.doAutoDownstreamKeyer(1); + break; + case 70: + AtemSwitcher.doAutoDownstreamKeyer(2); + break; + + case 71: + AtemSwitcher.changeAuxState(1, 1); + break; + case 72: + AtemSwitcher.changeAuxState(1, 16); + break; + case 73: + AtemSwitcher.changeAuxState(2, 1); + break; + case 74: + AtemSwitcher.changeAuxState(2, 16); + break; + case 75: + AtemSwitcher.changeAuxState(3, 1); + break; + case 76: + AtemSwitcher.changeAuxState(3, 16); + break; + + case 77: + AtemSwitcher.changeColorValue(1, 2011, 246, 535); + break; + case 78: + AtemSwitcher.changeColorValue(2, 54, 792, 497); + break; + case 79: + AtemSwitcher.changeColorValue(1, 0, 0, 30); + AtemSwitcher.changeColorValue(2, 0, 0, 70); + break; + + case 80: + AtemSwitcher.settingsMemorySave(); + Serial.println("SAVED SETTINGS"); + break; + case 81: + AtemSwitcher.settingsMemoryClear(); + Serial.println("CLEARED SETTINGS"); + break; + + default: + c=0; + break; + } + } + buttonState = true; + } + else { + buttonState = false; + } +} diff --git a/examples/ATEMbasicControl/ATEMbasicControl.pde b/examples/ATEMbasicControl/ATEMbasicControl.pde index 8454ec1..beda8d3 100644 --- a/examples/ATEMbasicControl/ATEMbasicControl.pde +++ b/examples/ATEMbasicControl/ATEMbasicControl.pde @@ -89,15 +89,15 @@ void loop() { // Write the Tally LEDS: - digitalWrite(4, !(AtemSwitcher.getATEM_PrgI()==1)); - digitalWrite(5, !(AtemSwitcher.getATEM_PrgI()==2)); + digitalWrite(4, !AtemSwitcher.getProgramTally(1)); + digitalWrite(5, !AtemSwitcher.getProgramTally(2)); if (digitalRead(2)) { if (pushButton !=2) { pushButton=2; Serial.println("Select 1"); - AtemSwitcher.setATEM_PrvI(1); + AtemSwitcher.changePreviewInput(1); } } else if (pushButton==2) { pushButton = 0; @@ -106,7 +106,7 @@ void loop() { if (pushButton !=3) { pushButton=3; Serial.println("Select 2"); - AtemSwitcher.setATEM_PrvI(2); + AtemSwitcher.changePreviewInput(2); } } else if (pushButton==3) { pushButton = 0; @@ -115,7 +115,7 @@ void loop() { if (pushButton !=7) { pushButton=7; Serial.println("Cut"); - AtemSwitcher.send_cut(); + AtemSwitcher.doCut(); } } else if (pushButton==7) { pushButton = 0; diff --git a/examples/ATEMbasicControl/Breadboard circuit for ATEM basic control.pdf b/examples/ATEMbasicControl/Breadboard circuit for ATEM basic control.pdf index bbbb2f6..2db5cf7 100644 Binary files a/examples/ATEMbasicControl/Breadboard circuit for ATEM basic control.pdf and b/examples/ATEMbasicControl/Breadboard circuit for ATEM basic control.pdf differ diff --git a/examples/ATEMmonitor/ATEMmonitor.pde b/examples/ATEMmonitor/ATEMmonitor.pde index b4cf579..8628a25 100644 --- a/examples/ATEMmonitor/ATEMmonitor.pde +++ b/examples/ATEMmonitor/ATEMmonitor.pde @@ -62,6 +62,7 @@ void setup() { // Start the Ethernet, Serial (debugging) and UDP: Ethernet.begin(mac,ip); Serial.begin(9600); + Serial.println("Serial started."); // Initialize a connection to the switcher: AtemSwitcher.serialOutput(true); @@ -71,4 +72,4 @@ void setup() { void loop() { // Check for packets, respond to them etc. Keeping the connection alive! AtemSwitcher.runLoop(); -} +} \ No newline at end of file