void ATEM::fadeToBlackActivate() { _wipeCleanPacketBuffer(); _packetBuffer[1] = 0x02; _packetBuffer[2] = 0x58; _packetBuffer[3] = 0x99; _sendPacketBufferCmdData("FtbA", 4); // Reflected back from ATEM in "FtbS" }
void ATEM::doAuto() { _wipeCleanPacketBuffer(); _packetBuffer[1] = 0x32; _packetBuffer[2] = 0x16; _packetBuffer[3] = 0x02; _sendPacketBufferCmdData("DAut", 4); }
void ATEM::doCut() { _wipeCleanPacketBuffer(); _packetBuffer[1] = 0xef; _packetBuffer[2] = 0xbf; _packetBuffer[3] = 0x5f; _sendPacketBufferCmdData("DCut", 4); }
void ATEM::changePreviewInput(uint8_t inputNumber) { // TODO: Validate that input number exists on current model! _wipeCleanPacketBuffer(); _packetBuffer[1] = inputNumber; _sendPacketBufferCmdData("CPvI", 4); }
/** * Initiating connection handshake to the ATEM switcher * If useFixedPortNumber is true, the same port number will be used on subsequent connects, otherwise - and recommended - a new, random port number is used. */ void ATEMbase::connect(const boolean useFixedPortNumber) { _localPacketIdCounter = 0; // Init localPacketIDCounter to 0; _initPayloadSent = false; // Will be true after initial payload of data is delivered (regular 12-byte ping packages are transmitted.) _hasInitialized = false; // Will be true after initial payload of data is resent and received well _isConnected = false; // Will be true after the initial hello-package handshakes. _sessionID = 0x53AB; // Temporary session ID - a new will be given back from ATEM. _lastContact = millis(); // Setting this, because even though we haven't had contact, it constitutes an attempt that should be responded to at least memset(_missedInitializationPackages, 0xFF, (ATEM_maxInitPackageCount+7)/8); _initPayloadSentAtPacketId = ATEM_maxInitPackageCount; // The max value it can be uint16_t portNumber = useFixedPortNumber ? _localPort : random(50100,65300); _Udp.begin(portNumber); // Send connectString to ATEM: if (_serialOutput) { Serial.print(F("Sending connect packet to ATEM switcher on IP ")); Serial.print(_switcherIP); Serial.print(F(" from port ")); Serial.println(portNumber); } _wipeCleanPacketBuffer(); _createCommandHeader(ATEM_headerCmd_HelloPacket, 12+8); _packetBuffer[12] = 0x01; // This seems to be what the client should send upon first request. _packetBuffer[9] = 0x3a; // This seems to be what the client should send upon first request. _sendPacketBuffer(20); }
void ATEM::changeUpstreamKeyOn(uint8_t keyer, bool state) { if (keyer>=1 && keyer<=4) { // Todo: Should match available keyers depending on model? _wipeCleanPacketBuffer(); _packetBuffer[1] = keyer-1; _packetBuffer[2] = state ? 0x01 : 0x00; _packetBuffer[3] = 0x90; _sendPacketBufferCmdData("CKOn", 4); // Reflected back from ATEM in "KeOn" } }
/** * Initiating connection handshake to Sony Visca UDP: By resetting setting the sequence counter * This also puts the camera "online" which indicates that a given camera should be attempted to keep a connection to */ void ClientSonyVISCAoverIP::connect(uint8_t cam) { if (camNumOK(cam)) { // valid range for VISCA _camOnline[cam - 1] = true; // Turn a camera online! _localPacketIdCounter[cam - 1] = 0; // Init localPacketIDCounter to 0; _lastContact[cam - 1] = millis(); _pushedStateUpdate[cam - 1] = false; _isConnected[cam - 1] = false; // Will be true after the initial hello-package handshakes. _nextStateUpdate[cam - 1] = 0; // _hasInitialized = false; // TODO: Will be true after initial payload of data is resent and received well. Need to implement this at connect time. // Send connectString to camera: if (_serialOutput) { Serial << F("Sending reset counter packet to Sony Visca camera #") << cam << "\n"; } _wipeCleanPacketBuffer(); _packetBuffer[8] = 0x01; _createCommandHeader(cam, 0x0200, 1); _sendPacketBuffer(cam, 8 + 1); } }
/** * Sends next command in command buffer for all connected cameras */ void ClientSonyVISCAoverIP::sendCommand(uint8_t cam) { if (camNumOK(cam) && _isConnected[cam - 1]) { // valid range for VISCA if (_camOnline[cam - 1]) { int sendIndex = -1; if (_camLastTX[cam - 1] > 0) { // Waiting for answer if (hasTimedOut(_camLastTX[cam - 1], 200)) { // 200 ms wait for answer - could be a bit shorter? // retransmit: TODO // Retransmits seems to be dependent on other controllers messing with the camera - at least I had a very "stable" pattern of retransmits when Sonys controller was not connected - exactly every 10th package needed a retransmit.... // But if other controllers were connected it was a different story. Hmm... well, actually I guess retransmits could be avoided if we kept track of whether the buffer has also COMPLETED an instruction. We can send a new instruction as soon as we get ack, but it may not take a new instruction before it has completed both buffers. // However, we couldn't know if OTHER controllers are sending stuff to the camera and therefore we cannot know for sure if buffers are available for our incoming commands - hence we always need to handle retransmits. But assuming we don't have other controllers, we could at least operate more clearly in this regard. // Also consider if the IF_clear command should ever be used...? sendIndex = _applicationCommandBufferCurrentIndex[cam - 1]; Serial << "RETRANSMIT INDEX: " << sendIndex << "\n"; } } else { if (_applicationCommandBufferLastPositionAdded[cam - 1] != _applicationCommandBufferCurrentIndex[cam - 1]) { // Something in buffer: sendIndex = _applicationCommandBufferCurrentIndex[cam - 1] = (_applicationCommandBufferCurrentIndex[cam - 1] + 1) % SONYVISCAIP_comBufSize; // Serial << "New index to send: " << sendIndex << "\n"; } } if (sendIndex >= 0) { _wipeCleanPacketBuffer(); _packetBuffer[8] = 0x81; for (uint8_t a = 0; a < 15; a++) { _packetBuffer[9 + a] = _applicationCommandBuffer[cam - 1][sendIndex][a]; if (_applicationCommandBuffer[cam - 1][sendIndex][a] == 0xFF) { // Found end: _createCommandHeader(cam, _applicationCommandBuffer[cam - 1][sendIndex][0] == 0x01 ? 0x0100 : (_applicationCommandBuffer[cam - 1][sendIndex][0] == 0x09 ? 0x0110 : 0x0000), a + 2); // Serial << "Sending package, long " << (a + 2) << "\n"; _sendPacketBuffer(cam, 8 + a + 2); _camLastTX[cam - 1] = millis(); break; } } } } } }
void ATEMbase::runLoop(uint16_t delayTime) { static bool neverConnected = true; if (neverConnected) { neverConnected = false; connect(); // Serial.println("Connecting first time..."); } unsigned long enterTime = millis(); static boolean waitingForIncoming = false; do { while(true) { // Iterate until UDP buffer is empty uint16_t packetSize = _Udp.parsePacket(); if (_Udp.available()) { _Udp.read(_packetBuffer,12); // Read header _sessionID = word(_packetBuffer[2], _packetBuffer[3]); uint8_t headerBitmask = _packetBuffer[0]>>3; _lastRemotePacketID = word(_packetBuffer[10],_packetBuffer[11]); if (_lastRemotePacketID < ATEM_maxInitPackageCount) { _missedInitializationPackages[_lastRemotePacketID>>3] &= ~(B1<<(_lastRemotePacketID&0x07)); } uint16_t packetLength = word(_packetBuffer[0] & B00000111, _packetBuffer[1]); if (packetSize==packetLength) { // Just to make sure these are equal, they should be! _lastContact = millis(); waitingForIncoming = false; if (headerBitmask & ATEM_headerCmd_HelloPacket) { // Respond to "Hello" packages: _isConnected = true; // _packetBuffer[12] The ATEM will return a "2" in this return package of same length. If the ATEM returns "3" it means "fully booked" (no more clients can connect) and a "4" seems to be a kind of reconnect (seen when you drop the connection and the ATEM desperately tries to figure out what happened...) // _packetBuffer[15] This number seems to increment with about 3 each time a new client tries to connect to ATEM. It may be used to judge how many client connections has been made during the up-time of the switcher? _wipeCleanPacketBuffer(); _createCommandHeader(ATEM_headerCmd_Ack, 12); _packetBuffer[9] = 0x03; // This seems to be what the client should send upon first request. _sendPacketBuffer(12); } // 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... // The QT lib uses the "InCm" command to indicate this, but in the latest version of the firmware (2.14) // all the camera control information comes AFTER this command, so it's not a clear ending token anymore. // However, I'm not sure if I checked the _lastRemotePacketID of the packages with the additional camera control info - if it was a resend, // "InCm" may still indicate the number of the last init-package and that's all I need to request the missing ones.... // BTW: It has been observed on an old 10Mbit hub that packages could arrive in a different order than sent and this may // mess things up a bit on the initialization. So it's recommended to has as direct routes as possible. if(!_initPayloadSent && packetSize == 12 && _lastRemotePacketID>1) { _initPayloadSent = true; _initPayloadSentAtPacketId = _lastRemotePacketID; #if ATEM_debug if (_serialOutput & 0x80) { Serial.print(F("_initPayloadSent=TRUE @rpID ")); Serial.println(_initPayloadSentAtPacketId); Serial.print(F("Session ID: ")); Serial.println(_sessionID, DEC); } #endif } if (_initPayloadSent && (headerBitmask & ATEM_headerCmd_AckRequest) && (_hasInitialized || !(headerBitmask & ATEM_headerCmd_Resend))) { // Respond to request for acknowledge (and to resends also, whatever... _wipeCleanPacketBuffer(); _createCommandHeader(ATEM_headerCmd_Ack, 12, _lastRemotePacketID); _sendPacketBuffer(12); #if ATEM_debug if (_serialOutput & 0x80) { Serial.print(F("rpID: ")); Serial.print(_lastRemotePacketID, DEC); Serial.print(F(", Head: 0x")); Serial.print(headerBitmask, HEX); Serial.print(F(", Len: ")); Serial.print(packetLength, DEC); Serial.print(F(" bytes")); Serial.println(F(" - ACK!")); } else #endif if (_serialOutput>1) { Serial.print(F("rpID: ")); Serial.print(_lastRemotePacketID, DEC); Serial.println(F(" - ACK!")); } } else if(_initPayloadSent && (headerBitmask & ATEM_headerCmd_RequestNextAfter) && _hasInitialized) { // ATEM is requesting a previously sent package which must have dropped out of the order. We return an empty one so the ATEM doesnt' crash (which some models will, if it doesn't get an answer before another 63 commands gets sent from the controller.) uint8_t b1 = _packetBuffer[6]; uint8_t b2 = _packetBuffer[7]; _wipeCleanPacketBuffer(); _createCommandHeader(ATEM_headerCmd_Ack, 12, 0); _packetBuffer[0] = ATEM_headerCmd_AckRequest << 3; // Overruling this. A small trick because createCommandHeader shouldn't increment local package ID counter _packetBuffer[10] = b1; _packetBuffer[11] = b2; _sendPacketBuffer(12); if (_serialOutput>1) { Serial.print(F("ATEM asking to resend ")); Serial.println((b1<<8)|b2, DEC); } } else { #if ATEM_debug if (_serialOutput & 0x80) { Serial.print(F("rpID: ")); Serial.print(_lastRemotePacketID, DEC); Serial.print(F(", Head: 0x")); Serial.print(headerBitmask, HEX); Serial.print(F(", Len: ")); Serial.print(packetLength, DEC); Serial.println(F(" bytes")); } else #endif if (_serialOutput>1) { Serial.print(F("rpID: ")); Serial.println(_lastRemotePacketID, DEC); } } if (!(headerBitmask & ATEM_headerCmd_HelloPacket) && packetLength>12) { _parsePacket(packetLength); } } else { #if ATEM_debug if (_serialOutput & 0x80) { Serial.print(F("ERROR: Packet size mismatch: ")); Serial.print(packetSize, DEC); Serial.print(F(" != ")); Serial.println(packetLength, DEC); } #endif // Flushing: while(_Udp.available()) { _Udp.read(_packetBuffer, ATEM_packetBufferLength); } } } else break; }