From 693b5e2030a23f47c80d4b951d8f384d4f79e9d0 Mon Sep 17 00:00:00 2001 From: Kasper Skaarhoj Date: Sat, 14 Jan 2012 01:26:38 +0100 Subject: [PATCH] first commit --- ATEM.cpp | 300 +++++++++++++++++++++++++++ ATEM.h | 71 +++++++ examples/.DS_Store | Bin 0 -> 6148 bytes examples/ATEMmonitor/ATEMmonitor.pde | 16 ++ keywords.txt | 3 + 5 files changed, 390 insertions(+) create mode 100755 ATEM.cpp create mode 100755 ATEM.h create mode 100644 examples/.DS_Store create mode 100644 examples/ATEMmonitor/ATEMmonitor.pde create mode 100644 keywords.txt diff --git a/ATEM.cpp b/ATEM.cpp new file mode 100755 index 0000000..6f6ca81 --- /dev/null +++ b/ATEM.cpp @@ -0,0 +1,300 @@ +/* +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/. + +*/ + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#include "ATEM.h" + +ATEM::ATEM(IPAddress ip, uint16_t localPort){ + // Set up Udp communication object: + EthernetUDP Udp; + _Udp = Udp; + + _switcherIP = ip; // Set switcher IP address + _localPort = localPort; // Set local port (just a random number I picked) +} + +void ATEM::connect() { + + _localPacketIdCounter = 1; // Init localPacketIDCounter to 1; + _hasInitialized = false; + _Udp.begin(_localPort); + + // Send connectString to ATEM: + // TODO: Describe packet contents according to rev.eng. API + byte connectHello[] = { + 0x10, 0x14, 0x53, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + _Udp.beginPacket(_switcherIP, 9910); + _Udp.write(connectHello,20); + _Udp.endPacket(); + + // Waiting for the ATEM to answer back with a packet 20 bytes long. + // According to packet analysis with WireShark, this feedback from ATEM + // comes within a few microseconds! + uint16_t packetSize = 0; + while(packetSize!=20) { + packetSize = _Udp.parsePacket(); + } + + // Read the response packet. We will only subtract the session ID + // According to packet analysis with WireShark, this feedback from ATEM + // comes a few microseconds after our connect invitation above. Two packets immediately follow each other. + // After approx. 200 milliseconds a third packet is sent from ATEM - a sort of re-sent because it gets impatient. + // And it seems that THIS third packet is the one we actually read and respond to. In other words, I believe that + // the ethernet interface on Arduino actually misses the first two for some reason! + while(!_Udp.available()){} // Waiting.... TODO: Implement some way to exit if there is no answer! + + _Udp.read(_packetBuffer,20); + _sessionID = _packetBuffer[15]; + + + // 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 }; + _Udp.write(connectHelloAnswerString,12); + _Udp.endPacket(); +} + +void ATEM::runLoop() { + + // WARNING: + // It can cause severe timing problems using "slow" functions such as Serial.print*() + // in the runloop, in particular during "boot" where the ATEM delivers some 10-20 kbytes of system status info which + // must exit the RX-buffer quite fast. Therefore, using Serial.print for debugging in this + // critical phase will in it self affect program execution! + + // Limit of the RX buffer of the Ethernet interface is another general issue. + // When ATEM sends the initial system status packets (10-20 kbytes), they are sent with a few microseconds in between + // The RX buffer of the Ethernet interface on Arduino simply does not have the kapacity to take more than 2k at a time. + // This means, that we only receive the first packet, the others seems to be discarded. Luckily most information we like to + // know about is in the first packet (and some in the second, the rest is probably thumbnails for the media player). + // It may be possible to bump up this buffer to 4 or 8 k by simply re-configuring the amount of allowed sockets on the interface. + // For some more information from a guy seemingly having a similar issue, look here: + // http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1282170842 + + + + // If there's data available, read a packet + uint16_t packetSize = _Udp.parsePacket(); + if (_Udp.available() && packetSize !=0) { + + // Read packet header of 12 bytes: + _Udp.read(_packetBuffer, 12); + + // 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]); + 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 + // 2 = 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. + // 5 = "This is a response on your request". So set this when answering... + + if (packetSize==packetLength) { // Just to make sure these are equal, they should be! + + // If a packet is 12 bytes long it indicates that all the initial information + // has been delivered from the ATEM and we can begin to answer back on every request + // Currently we don't know any other way to decide if an answer should be sent back... + if(!_hasInitialized && packetSize == 12) { + _hasInitialized = true; + } + + if (packetLength > 12) { + _parsePacket(packetLength); + } + + // If we are initialized, lets answer back no matter what: + if (_hasInitialized && command_ACK) { + Serial.print("ACK, rpID: "); + Serial.println(remotePacketID, DEC); + + _sendAnswerPacket(remotePacketID); + } + + } else { + Serial.print("ERROR: Packet size mismatch: "); + Serial.print(packetSize, DEC); + Serial.print(" != "); + Serial.println(packetLength, DEC); + + // Flushing the buffer: + // TODO: Other way? _Udp.flush() ?? + while(_Udp.available()) { + _Udp.read(_packetBuffer, 96); + } + } + } +} + + +void ATEM::_parsePacket(uint16_t packetLength) { + + // 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) { + + // Read the length of segment (first word): + _Udp.read(_packetBuffer, 2); + uint16_t cmdLength = word(0, _packetBuffer[1]); + // If length of segment fits into buffer, lets read it, otherwise throw an error: + if (cmdLength>2 && cmdLength<=96) { + + // Read the rest of the segment: + _Udp.read(_packetBuffer, cmdLength-2); + + // Get the "command string", basically this is the 4 char variable name in the ATEM memory holding the various state values of the system: + 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: + if(strcmp(cmdStr, "PrgI") == 0) { // Program Bus status + _ATEM_PrgI = _packetBuffer[-2+8+1]; + Serial.print("Program Bus: "); + Serial.println(_packetBuffer[-2+8+1], DEC); + } + if(strcmp(cmdStr, "PrvI") == 0) { // Preview Bus status + _ATEM_PrvI = _packetBuffer[-2+8+1]; + Serial.print("Preview Bus: "); + Serial.println(_packetBuffer[-2+8+1], DEC); + } + + indexPointer+=cmdLength; + } else { + // Error, just get out of the loop ASAP: + Serial.print("ERROR: Command Size mismatch: "); + Serial.print(cmdLength, DEC); + indexPointer = 2000; + + // Flushing the buffer: + // TODO: Other way? _Udp.flush() ?? + while(_Udp.available()) { + _Udp.read(_packetBuffer, 96); + } + } + } +} + + +void ATEM::_sendAnswerPacket(uint16_t remotePacketID) { + + //Answer packet: + memset(_answer, 0, 12); // Using 12 bytes of answer buffer, setting to zeros. + _answer[2] = 0x80; // ??? API + _answer[3] = _sessionID; // Session ID + _answer[4] = remotePacketID/256; // Remote Packet ID, MSB + _answer[5] = remotePacketID%256; // Remote Packet ID, LSB + _answer[9] = 0x41; // ??? API + // The rest is zeros. + + // Create header: + uint16_t returnPacketLength = 10+2; + _answer[0] = returnPacketLength/256; + _answer[1] = returnPacketLength%256; + _answer[0] |= B10000000; + + // Send connectAnswerString to ATEM: + _Udp.beginPacket(_switcherIP, 9910); + _Udp.write(_answer,returnPacketLength); + _Udp.endPacket(); +} + +void ATEM::_sendCommandPacket(char cmd[4], uint8_t commandBytes[16], uint8_t cmdBytes) { + + if (cmdBytes <= 4) { // Currently, only a lenght of 4 - can be extended, but then the _answer buffer must be prolonged as well (to more than 24) + //Answer packet preparations: + memset(_answer, 0, 24); + _answer[2] = 0x80; // ??? API + _answer[3] = _sessionID; // Session ID + _answer[10] = _localPacketIdCounter/256; // Remote Packet ID, MSB + _answer[11] = _localPacketIdCounter%256; // Remote Packet ID, LSB + + // The rest is zeros. + + // Command identifier (4 bytes, after header (12 bytes) and local segment length (4 bytes)): + int i; + for (i=0; i<4; i++) { + _answer[12+4+i] = cmd[i]; + } + + // Command value (after command): + for (i=0; i= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + + +#include + + +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 + + 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. + + 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 + + public: + ATEM(IPAddress ip, uint16_t localPort); + void connect(); + void runLoop(); + + private: + void _sendAnswerPacket(uint16_t remotePacketID); + void _parsePacket(uint16_t packetLength); + 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(); +}; + +#endif + diff --git a/examples/.DS_Store b/examples/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + +Morse morse(13); + +void setup() +{ +} + +void loop() +{ + morse.dot(); morse.dot(); morse.dot(); + morse.dash(); morse.dash(); morse.dash(); + morse.dot(); morse.dot(); morse.dot(); + delay(3000); +} + diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..4eddf4c --- /dev/null +++ b/keywords.txt @@ -0,0 +1,3 @@ +Morse KEYWORD1 +dash KEYWORD2 +dot KEYWORD2