Start
Start
This commit is contained in:
926
libraries/ATEMbase/ATEMbase.cpp
Normal file
926
libraries/ATEMbase/ATEMbase.cpp
Normal file
@@ -0,0 +1,926 @@
|
||||
/*
|
||||
Copyright 2012-2014 Kasper Skårhøj, SKAARHOJ K/S, kasper@skaarhoj.com
|
||||
|
||||
This file is part of the Blackmagic Design ATEM Client 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/.
|
||||
|
||||
|
||||
IMPORTANT: If you want to use this library in your own projects and/or products,
|
||||
please play a fair game and heed the license rules! See our web page for a Q&A so
|
||||
you can keep a clear conscience: http://skaarhoj.com/about/licenses/
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include "ATEMbase.h"
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
ATEMbase::ATEMbase(){}
|
||||
|
||||
/**
|
||||
* Setting up IP address for the switcher (and local port to send packets from)
|
||||
* Using local port here is deprecated. Rather let the library pick a random one
|
||||
*/
|
||||
void ATEMbase::begin(const IPAddress ip){
|
||||
begin(ip, random(50100,65300));
|
||||
}
|
||||
void ATEMbase::begin(const IPAddress ip, const uint16_t localPort){
|
||||
|
||||
neverConnected = true;
|
||||
waitingForIncoming = false;
|
||||
|
||||
// Set up Udp communication object:
|
||||
#ifdef ESP8266
|
||||
WiFiUDP Udp;
|
||||
#else
|
||||
EthernetUDP Udp;
|
||||
#endif
|
||||
|
||||
_Udp = Udp;
|
||||
|
||||
_switcherIP = ip; // Set switcher IP address
|
||||
_localPort = localPort; // Set default local port
|
||||
|
||||
_lastContact = 0;
|
||||
_serialOutput = 0;
|
||||
|
||||
resetCommandBundle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiating connection handshake to the ATEM switcher
|
||||
*/
|
||||
void ATEMbase::connect() {
|
||||
connect(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps connection to the switcher alive
|
||||
* 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 ATEMbase::runLoop() {
|
||||
runLoop(0);
|
||||
}
|
||||
void ATEMbase::runLoop(uint16_t delayTime) {
|
||||
if (neverConnected) {
|
||||
neverConnected = false;
|
||||
connect();
|
||||
// Serial.println("Connecting first time...");
|
||||
}
|
||||
|
||||
unsigned long enterTime = millis();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// After initialization, we check which packages were missed and ask for them:
|
||||
if (!_hasInitialized && _initPayloadSent && !waitingForIncoming) {
|
||||
for(uint8_t i=1; i<_initPayloadSentAtPacketId; i++) {
|
||||
if(i <= ATEM_maxInitPackageCount) {
|
||||
if (_missedInitializationPackages[i>>3] & (B1<<(i & 0x7))) {
|
||||
|
||||
#if ATEM_debug
|
||||
if (_serialOutput & 0x80) {
|
||||
Serial.print(F("Asking for package "));
|
||||
Serial.println(i, DEC);
|
||||
}
|
||||
#endif
|
||||
_wipeCleanPacketBuffer();
|
||||
_createCommandHeader(ATEM_headerCmd_RequestNextAfter, 12);
|
||||
_packetBuffer[6] = highByte(i-1); // Resend Packet ID, MSB
|
||||
_packetBuffer[7] = lowByte(i-1); // Resend Packet ID, LSB
|
||||
_packetBuffer[8] = 0x01;
|
||||
|
||||
_sendPacketBuffer(12);
|
||||
waitingForIncoming = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!waitingForIncoming) {
|
||||
_hasInitialized = true;
|
||||
if (_serialOutput) {
|
||||
Serial.println(F("ATEM _hasInitialized = TRUE"));
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (delayTime>0 && !hasTimedOut(enterTime,delayTime));
|
||||
|
||||
|
||||
// If connection is gone anyway, try to reconnect:
|
||||
if (hasTimedOut(_lastContact, 5000)) {
|
||||
if (_serialOutput) Serial.println(F("Connection to ATEM Switcher has timed out - reconnecting!"));
|
||||
connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last Remote Packet ID
|
||||
*/
|
||||
uint16_t ATEMbase::getATEM_lastRemotePacketId() {
|
||||
return _lastRemotePacketID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ATEM session ID
|
||||
*/
|
||||
uint16_t ATEMbase::getSessionID() {
|
||||
return _sessionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, we had a response from the switcher when trying to send a hello packet.
|
||||
*/
|
||||
bool ATEMbase::isConnected() {
|
||||
return _isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, the initial handshake and "stressful" information exchange has occured and now the switcher connection should be ready for operation.
|
||||
*/
|
||||
bool ATEMbase::hasInitialized() {
|
||||
return _hasInitialized;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**************
|
||||
*
|
||||
* Buffer work
|
||||
*
|
||||
**************/
|
||||
|
||||
void ATEMbase::_createCommandHeader(const uint8_t headerCmd, const uint16_t lengthOfData) {
|
||||
_createCommandHeader(headerCmd, lengthOfData, 0);
|
||||
}
|
||||
void ATEMbase::_createCommandHeader(const uint8_t headerCmd, const uint16_t lengthOfData, const uint16_t remotePacketID) {
|
||||
|
||||
_packetBuffer[0] = (headerCmd << 3) | (highByte(lengthOfData) & 0x07); // Command bits + length MSB
|
||||
_packetBuffer[1] = lowByte(lengthOfData); // length LSB
|
||||
|
||||
_packetBuffer[2] = highByte(_sessionID); // Session ID
|
||||
_packetBuffer[3] = lowByte(_sessionID); // Session ID
|
||||
|
||||
_packetBuffer[4] = highByte(remotePacketID); // Remote Packet ID, MSB
|
||||
_packetBuffer[5] = lowByte(remotePacketID); // Remote Packet ID, LSB
|
||||
|
||||
if(!(headerCmd & (ATEM_headerCmd_HelloPacket | ATEM_headerCmd_Ack | ATEM_headerCmd_RequestNextAfter))) {
|
||||
_localPacketIdCounter++;
|
||||
|
||||
// if ((_localPacketIdCounter & 0xF) == 0xF) _localPacketIdCounter++; // Uncommenting this line will jump the local package ID counter every 15 command - thereby introducing a stress test of the robustness of the "resent package" function from the ATEM switcher.
|
||||
|
||||
_packetBuffer[10] = highByte(_localPacketIdCounter); // Local Packet ID, MSB
|
||||
_packetBuffer[11] = lowByte(_localPacketIdCounter); // Local Packet ID, LSB
|
||||
}
|
||||
}
|
||||
void ATEMbase::_sendPacketBuffer(uint8_t length) {
|
||||
_Udp.beginPacket(_switcherIP, 9910);
|
||||
_Udp.write(_packetBuffer,length);
|
||||
_Udp.endPacket(); // TODO: Figure out why this may hang!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all zeros in packet buffer:
|
||||
*/
|
||||
void ATEMbase::_wipeCleanPacketBuffer() {
|
||||
memset(_packetBuffer, 0, ATEM_packetBufferLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from UDP channel to buffer. Will fill the buffer to the max or to the size of the current segment being parsed
|
||||
* Returns false if there are no more bytes, otherwise true
|
||||
*/
|
||||
bool ATEMbase::_readToPacketBuffer() {
|
||||
return _readToPacketBuffer(ATEM_packetBufferLength);
|
||||
}
|
||||
bool ATEMbase::_readToPacketBuffer(uint8_t maxBytes) {
|
||||
maxBytes = maxBytes<=ATEM_packetBufferLength ? maxBytes : ATEM_packetBufferLength;
|
||||
int remainingBytes = _cmdLength-8-_cmdPointer;
|
||||
|
||||
if (remainingBytes>0) {
|
||||
if (remainingBytes <= maxBytes) {
|
||||
_Udp.read(_packetBuffer, remainingBytes);
|
||||
_cmdPointer+= remainingBytes;
|
||||
return false; // Returns false if finished.
|
||||
} else {
|
||||
_Udp.read(_packetBuffer, maxBytes);
|
||||
_cmdPointer+= maxBytes;
|
||||
return true; // Returns true if there are still bytes to be read.
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ATEMbase::_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; // 12 bytes has already been read from the packet...
|
||||
while (indexPointer < packetLength) {
|
||||
|
||||
// Read the length of segment (first word):
|
||||
_Udp.read(_packetBuffer, 8);
|
||||
_cmdLength = word(_packetBuffer[0], _packetBuffer[1]);
|
||||
_cmdPointer = 0;
|
||||
|
||||
// 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[4], _packetBuffer[5], _packetBuffer[6], _packetBuffer[7], '\0'};
|
||||
|
||||
// If length of segment larger than 8 (should always be...!)
|
||||
if (_cmdLength>8) {
|
||||
_parseGetCommands(cmdStr);
|
||||
|
||||
while (_readToPacketBuffer()) {} // Empty, if not done yet.
|
||||
indexPointer+=_cmdLength;
|
||||
} else {
|
||||
indexPointer = 2000;
|
||||
#if ATEM_debug
|
||||
if (_serialOutput & 0x80) Serial.println(F("Bad CMD length, flushing..."));
|
||||
#endif
|
||||
|
||||
// Flushing the buffer:
|
||||
while(_Udp.available()) {
|
||||
_Udp.read(_packetBuffer, ATEM_packetBufferLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be overloaded in subclasses in order to handle specific get-commands
|
||||
*/
|
||||
void ATEMbase::_parseGetCommands(const char *cmdString) {
|
||||
// uint8_t mE, keyer, mediaPlayer, aUXChannel, windowIndex, multiViewer, memory, colorGenerator, box;
|
||||
// uint16_t audioSource, videoSource;
|
||||
// long temp;
|
||||
|
||||
uint8_t numberOfReads=1;
|
||||
while(_readToPacketBuffer()) {
|
||||
numberOfReads++;
|
||||
}
|
||||
|
||||
#if ATEM_debug
|
||||
if (_serialOutput & 0x80) {
|
||||
Serial.print(cmdString);
|
||||
Serial.print(", len: ");
|
||||
Serial.print(_cmdLength);
|
||||
Serial.print(", rds: ");
|
||||
Serial.println(numberOfReads);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ATEMbase::_prepareCommandPacket(const char *cmdString, uint8_t cmdBytes, bool indexMatch) {
|
||||
|
||||
// First, in case of a command bundle, check if indexes are different OR if it's an entirely different command, then increase offset to accommodate new command:
|
||||
if (_cBundle) {
|
||||
if (_returnPacketLength>0 && (!indexMatch || strncmp_P((char *)(_packetBuffer+12+_cBBO+4), cmdString, 4))) {
|
||||
_cBBO = _returnPacketLength-12;
|
||||
}
|
||||
} else {
|
||||
_wipeCleanPacketBuffer(); // For command bundles, this is already done...
|
||||
}
|
||||
|
||||
_returnPacketLength = 12+_cBBO+(4+4+cmdBytes);
|
||||
|
||||
// Because we increased length of command, we need to check for buffer overflow:
|
||||
if (_returnPacketLength > ATEM_packetBufferLength) {
|
||||
Serial.println(F("FATAL ERROR: Packet Buffer Overflow in the ATEM Library! Too long or too many commands bundled!\n HALT"));
|
||||
while(true){} // STOP!
|
||||
}
|
||||
|
||||
// Copy Command String:
|
||||
if (strlen_P(cmdString)==4) {
|
||||
strncpy_P((char *)(_packetBuffer+12+_cBBO+4), cmdString, 4);
|
||||
}
|
||||
#if ATEM_debug
|
||||
else Serial.println(F("Command Length > 4 ERROR"));
|
||||
#endif
|
||||
|
||||
// Command length:
|
||||
_packetBuffer[12+_cBBO] = 0; // MSB - but it's always under 256, so....
|
||||
_packetBuffer[12+1+_cBBO] = 4+4+cmdBytes; // LSB
|
||||
}
|
||||
|
||||
void ATEMbase::_finishCommandPacket() {
|
||||
if (!_cBundle) {
|
||||
|
||||
_createCommandHeader(ATEM_headerCmd_AckRequest, _returnPacketLength);
|
||||
_sendPacketBuffer(_returnPacketLength);
|
||||
_returnPacketLength = 0;
|
||||
|
||||
} else {
|
||||
// Debugging info:
|
||||
/* for(uint8_t a=0; a<_returnPacketLength; a++) {
|
||||
if (_packetBuffer[a]<16) Serial.print("0");
|
||||
Serial.print(_packetBuffer[a], HEX);
|
||||
Serial.print(F("-"));
|
||||
}
|
||||
Serial.println();
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**************
|
||||
*
|
||||
* Utilities from SkaarhojTools class:
|
||||
*
|
||||
**************/
|
||||
|
||||
/**
|
||||
* Setter method: If _serialOutput is set, the library may use Serial.print() to give away information about its operation - mostly for debugging.
|
||||
* 0= no output
|
||||
* 1= normal output (info)
|
||||
* 2= verbose
|
||||
* &0x80 (bit 7 set): verbose initial connection information
|
||||
*/
|
||||
void ATEMbase::serialOutput(uint8_t level) {
|
||||
_serialOutput = level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeout check
|
||||
*/
|
||||
bool ATEMbase::hasTimedOut(unsigned long time, unsigned long timeout) {
|
||||
if ((unsigned long)(time + timeout) <= (unsigned long)millis()) { // This should "wrap around" if time+timout is larger than the size of unsigned-longs, right?
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
uint8_t ATEMbase::getATEMmodel() {
|
||||
return _ATEMmodel;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
float ATEMbase::audioWord2Db(uint16_t input) { // -48 to +6 output
|
||||
// Formular: log10(input/128)*20-48;
|
||||
if (input<=32) return -60;
|
||||
//return (log10(input)-2.1072099696)*20-48;
|
||||
// Better way?
|
||||
//return log10(input >> 5) * 20.0 - 60.0;
|
||||
|
||||
return log10((float)input/(1<<11) / 16.0) * 20.0;
|
||||
}
|
||||
uint16_t ATEMbase::audioDb2Word(float input) { // -48 to +6 input
|
||||
//return (float)pow(10,(input+48)/20)*128;
|
||||
//return (uint16_t)pow(10, (input + 60.0) / 20.0) << 5;
|
||||
|
||||
return pow(10, input/20.0) * 16.0 * (1<<11);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
uint8_t ATEMbase::getVideoSrcIndex(uint16_t videoSrc) {
|
||||
switch(videoSrc){
|
||||
case 0: // Black
|
||||
return 0;
|
||||
case 1: // Input 1
|
||||
return 1;
|
||||
case 2: // Input 2
|
||||
return 2;
|
||||
case 3: // Input 3
|
||||
return 3;
|
||||
case 4: // Input 4
|
||||
return 4;
|
||||
case 5: // Input 5
|
||||
return 5;
|
||||
case 6: // Input 6
|
||||
return 6;
|
||||
case 7: // Input 7
|
||||
return 7;
|
||||
case 8: // Input 8
|
||||
return 8;
|
||||
case 9: // Input 9
|
||||
return 9;
|
||||
case 10: // Input 10
|
||||
return 10;
|
||||
case 11: // Input 11
|
||||
return 11;
|
||||
case 12: // Input 12
|
||||
return 12;
|
||||
case 13: // Input 13
|
||||
return 13;
|
||||
case 14: // Input 14
|
||||
return 14;
|
||||
case 15: // Input 15
|
||||
return 15;
|
||||
case 16: // Input 16
|
||||
return 16;
|
||||
case 17: // Input 17
|
||||
return 17;
|
||||
case 18: // Input 18
|
||||
return 18;
|
||||
case 19: // Input 19
|
||||
return 19;
|
||||
case 20: // Input 20
|
||||
return 20;
|
||||
case 1000: // Color Bars
|
||||
return 21;
|
||||
case 2001: // Color 1
|
||||
return 22;
|
||||
case 2002: // Color 2
|
||||
return 23;
|
||||
case 3010: // Media Player 1
|
||||
return 24;
|
||||
case 3011: // Media Player 1 Key
|
||||
return 25;
|
||||
case 3020: // Media Player 2
|
||||
return 26;
|
||||
case 3021: // Media Player 2 Key
|
||||
return 27;
|
||||
case 4010: // Key 1 Mask
|
||||
return 28;
|
||||
case 4020: // Key 2 Mask
|
||||
return 29;
|
||||
case 4030: // Key 3 Mask
|
||||
return 30;
|
||||
case 4040: // Key 4 Mask
|
||||
return 31;
|
||||
case 5010: // DSK 1 Mask
|
||||
return 32;
|
||||
case 5020: // DSK 2 Mask
|
||||
return 33;
|
||||
case 6000: // Super Source
|
||||
return 34;
|
||||
case 7001: // Clean Feed 1
|
||||
return 35;
|
||||
case 7002: // Clean Feed 2
|
||||
return 36;
|
||||
case 8001: // Auxilary 1
|
||||
return 37;
|
||||
case 8002: // Auxilary 2
|
||||
return 38;
|
||||
case 8003: // Auxilary 3
|
||||
return 39;
|
||||
case 8004: // Auxilary 4
|
||||
return 40;
|
||||
case 8005: // Auxilary 5
|
||||
return 41;
|
||||
case 8006: // Auxilary 6
|
||||
return 42;
|
||||
case 10010: // ME 1 Prog
|
||||
return 43;
|
||||
case 10011: // ME 1 Prev
|
||||
return 44;
|
||||
case 10020: // ME 2 Prog
|
||||
return 45;
|
||||
case 10021: // ME 2 Prev
|
||||
return 46;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ATEMbase::getAudioSrcIndex(uint16_t audioSrc) {
|
||||
switch(audioSrc){
|
||||
case 1: // Input 1
|
||||
return 0;
|
||||
case 2: // Input 2
|
||||
return 1;
|
||||
case 3: // Input 3
|
||||
return 2;
|
||||
case 4: // Input 4
|
||||
return 3;
|
||||
case 5: // Input 5
|
||||
return 4;
|
||||
case 6: // Input 6
|
||||
return 5;
|
||||
case 7: // Input 7
|
||||
return 6;
|
||||
case 8: // Input 8
|
||||
return 7;
|
||||
case 9: // Input 9
|
||||
return 8;
|
||||
case 10: // Input 10
|
||||
return 9;
|
||||
case 11: // Input 11
|
||||
return 10;
|
||||
case 12: // Input 12
|
||||
return 11;
|
||||
case 13: // Input 13
|
||||
return 12;
|
||||
case 14: // Input 14
|
||||
return 13;
|
||||
case 15: // Input 15
|
||||
return 14;
|
||||
case 16: // Input 16
|
||||
return 15;
|
||||
case 17: // Input 17
|
||||
return 16;
|
||||
case 18: // Input 18
|
||||
return 17;
|
||||
case 19: // Input 19
|
||||
return 18;
|
||||
case 20: // Input 20
|
||||
return 19;
|
||||
case 1001: // XLR
|
||||
return 20;
|
||||
case 1101: // AES/EBU
|
||||
return 21;
|
||||
case 1201: // RCA
|
||||
return 22;
|
||||
case 2001: // MP1
|
||||
return 23;
|
||||
case 2002: // MP2
|
||||
return 24;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Translating a index to a video source
|
||||
*/
|
||||
uint16_t ATEMbase::getVideoIndexSrc(uint8_t index) {
|
||||
switch (index) {
|
||||
case 0: // Black
|
||||
return 0;
|
||||
case 1: // Input 1
|
||||
return 1;
|
||||
case 2: // Input 2
|
||||
return 2;
|
||||
case 3: // Input 3
|
||||
return 3;
|
||||
case 4: // Input 4
|
||||
return 4;
|
||||
case 5: // Input 5
|
||||
return 5;
|
||||
case 6: // Input 6
|
||||
return 6;
|
||||
case 7: // Input 7
|
||||
return 7;
|
||||
case 8: // Input 8
|
||||
return 8;
|
||||
case 9: // Input 9
|
||||
return 9;
|
||||
case 10: // Input 10
|
||||
return 10;
|
||||
case 11: // Input 11
|
||||
return 11;
|
||||
case 12: // Input 12
|
||||
return 12;
|
||||
case 13: // Input 13
|
||||
return 13;
|
||||
case 14: // Input 14
|
||||
return 14;
|
||||
case 15: // Input 15
|
||||
return 15;
|
||||
case 16: // Input 16
|
||||
return 16;
|
||||
case 17: // Input 17
|
||||
return 17;
|
||||
case 18: // Input 18
|
||||
return 18;
|
||||
case 19: // Input 19
|
||||
return 19;
|
||||
case 20: // Input 20
|
||||
return 20;
|
||||
case 21: // Color Bars
|
||||
return 1000;
|
||||
case 22: // Color 1
|
||||
return 2001;
|
||||
case 23: // Color 2
|
||||
return 2002;
|
||||
case 24: // Media Player 1
|
||||
return 3010;
|
||||
case 25: // Media Player 1 Key
|
||||
return 3011;
|
||||
case 26: // Media Player 2
|
||||
return 3020;
|
||||
case 27: // Media Player 2 Key
|
||||
return 3021;
|
||||
case 28: // Key 1 Mask
|
||||
return 4010;
|
||||
case 29: // Key 2 Mask
|
||||
return 4020;
|
||||
case 30: // Key 3 Mask
|
||||
return 4030;
|
||||
case 31: // Key 4 Mask
|
||||
return 4040;
|
||||
case 32: // DSK 1 Mask
|
||||
return 5010;
|
||||
case 33: // DSK 2 Mask
|
||||
return 5020;
|
||||
case 34: // Super Source
|
||||
return 6000;
|
||||
case 35: // Clean Feed 1
|
||||
return 7001;
|
||||
case 36: // Clean Feed 2
|
||||
return 7002;
|
||||
case 37: // Auxilary 1
|
||||
return 8001;
|
||||
case 38: // Auxilary 2
|
||||
return 8002;
|
||||
case 39: // Auxilary 3
|
||||
return 8003;
|
||||
case 40: // Auxilary 4
|
||||
return 8004;
|
||||
case 41: // Auxilary 5
|
||||
return 8005;
|
||||
case 42: // Auxilary 6
|
||||
return 8006;
|
||||
case 43: // ME 1 Prog
|
||||
return 10010;
|
||||
case 44: // ME 1 Prev
|
||||
return 10011;
|
||||
case 45: // ME 2 Prog
|
||||
return 10020;
|
||||
case 46: // ME 2 Prev
|
||||
return 10021;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Translating a index to a audio source
|
||||
*/
|
||||
uint16_t ATEMbase::getAudioIndexSrc(uint8_t index) {
|
||||
switch (index) {
|
||||
case 0: // Input 1
|
||||
return 1;
|
||||
case 1: // Input 2
|
||||
return 2;
|
||||
case 2: // Input 3
|
||||
return 3;
|
||||
case 3: // Input 4
|
||||
return 4;
|
||||
case 4: // Input 5
|
||||
return 5;
|
||||
case 5: // Input 6
|
||||
return 6;
|
||||
case 6: // Input 7
|
||||
return 7;
|
||||
case 7: // Input 8
|
||||
return 8;
|
||||
case 8: // Input 9
|
||||
return 9;
|
||||
case 9: // Input 10
|
||||
return 10;
|
||||
case 10: // Input 11
|
||||
return 11;
|
||||
case 11: // Input 12
|
||||
return 12;
|
||||
case 12: // Input 13
|
||||
return 13;
|
||||
case 13: // Input 14
|
||||
return 14;
|
||||
case 14: // Input 15
|
||||
return 15;
|
||||
case 15: // Input 16
|
||||
return 16;
|
||||
case 16: // Input 17
|
||||
return 17;
|
||||
case 17: // Input 18
|
||||
return 18;
|
||||
case 18: // Input 19
|
||||
return 19;
|
||||
case 19: // Input 20
|
||||
return 20;
|
||||
case 20: // XLR
|
||||
return 1001;
|
||||
case 21: // AES/EBU
|
||||
return 1101;
|
||||
case 22: // RCA
|
||||
return 1201;
|
||||
case 23: // MP1
|
||||
return 2001;
|
||||
case 24: // MP2
|
||||
return 2002;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ATEMbase::maxAtemSeriesVideoInputs() {
|
||||
return 47; // For the largest ATEM switcher, this is the number of video inputs. The max "index" number from the list above
|
||||
}
|
||||
|
||||
|
||||
void ATEMbase::commandBundleStart() {
|
||||
resetCommandBundle();
|
||||
_wipeCleanPacketBuffer();
|
||||
_cBundle = true;
|
||||
}
|
||||
void ATEMbase::commandBundleEnd() {
|
||||
if (_cBundle && _returnPacketLength > 0) {
|
||||
|
||||
_createCommandHeader(ATEM_headerCmd_AckRequest, _returnPacketLength);
|
||||
_sendPacketBuffer(_returnPacketLength);
|
||||
_returnPacketLength = 0;
|
||||
}
|
||||
resetCommandBundle();
|
||||
}
|
||||
void ATEMbase::resetCommandBundle() {
|
||||
_cBundle = false;
|
||||
_cBBO = 0;
|
||||
}
|
||||
Reference in New Issue
Block a user