├── ArduCastControl.cpp ├── ArduCastControl.h ├── LICENSE ├── README.md ├── authority_keys.pb.c ├── authority_keys.pb.h ├── cast_channel.pb.c ├── cast_channel.pb.h ├── examples └── simpleChromecastControl │ └── simpleChromecastControl.ino ├── library.json ├── logging.pb.c └── logging.pb.h /ArduCastControl.cpp: -------------------------------------------------------------------------------- 1 | #include "ArduCastControl.h" 2 | 3 | #include "cast_channel.pb.h" 4 | #include "authority_keys.pb.h" 5 | #include "logging.pb.h" 6 | 7 | #include "pb_common.h" 8 | #include "pb.h" 9 | #include "pb_encode.h" 10 | 11 | #include "string.h" 12 | 13 | const char CC_SOURCEID[] = "sender-0"; 14 | const char CC_MAIN_DESTIID[] = "receiver-0"; 15 | const char CC_NS_CONNECTION[] = "urn:x-cast:com.google.cast.tp.connection"; 16 | const char CC_NS_RECEIVER[] = "urn:x-cast:com.google.cast.receiver"; 17 | const char CC_NS_HEARTBEAT[] = "urn:x-cast:com.google.cast.tp.heartbeat"; 18 | const char CC_NS_MEDIA[] = "urn:x-cast:com.google.cast.media"; 19 | const char CC_MSG_CONNECT[] = "{\"type\": \"CONNECT\"}"; 20 | const char CC_MSG_PING[] = "{\"type\": \"PING\"}"; 21 | const char CC_MSG_GET_STATUS[] = "{\"type\": \"GET_STATUS\", \"requestId\": 1}"; 22 | 23 | //incomplet msgs as these need some args to the end 24 | const char CC_MSG_PLAY[] = "{\"type\": \"PLAY\", \"requestId\": 2, \"mediaSessionId\": "; 25 | const char CC_MSG_PAUSE[] = "{\"type\": \"PAUSE\", \"requestId\": 2, \"mediaSessionId\": "; 26 | const char CC_MSG_NEXT[] = "{\"type\": \"QUEUE_NEXT\", \"requestId\": 2, \"mediaSessionId\": "; 27 | const char CC_MSG_PREV[] = "{\"type\": \"QUEUE_PREV\", \"requestId\": 2, \"mediaSessionId\": "; 28 | const char CC_MSG_SET_VOL[] = "{\"type\": \"SET_VOLUME\", \"requestId\": 2, \"volume\": {\"level\": ";//this need double braces! 29 | const char CC_MSG_VOL_MUTE[] = "{\"type\": \"SET_VOLUME\", \"requestId\": 2, \"volume\": {\"muted\": ";//this need double braces! 30 | static char cc_msg_ctrl[128]; 31 | 32 | // void ArduCastConnection::init(WiFiClientSecure& client, int keepAlive, uint8_t *writeBuffer, int writeBufferSize){ 33 | // //this->client = client; 34 | // this->keepAlive = keepAlive; 35 | // this->writeBuffer = writeBuffer; 36 | // this->writeBufferSize = writeBufferSize; 37 | // connected = false; 38 | // } 39 | 40 | 41 | int ArduCastConnection::connect(const char* destinationId){ 42 | // Serial.printf("Connect to %s\n", destinationId); 43 | strncpy(destId, destinationId, sizeof(destId)); 44 | int err = writeMsg(CC_NS_CONNECTION, CC_MSG_CONNECT); 45 | pinged(); //do not ping immediately - we probably want to check status anyway 46 | connected = true; 47 | return err; 48 | } 49 | 50 | void ArduCastConnection::pinged(){ 51 | lastMsgAt = millis(); 52 | } 53 | 54 | void ArduCastConnection::setDisconnect(){ 55 | connected = false; 56 | } 57 | 58 | channelConnection_t ArduCastConnection::getConnectionStatus(){ 59 | if ( !client.connected() || !connected ) 60 | return CH_DISCONNECTED; 61 | if ( (unsigned long)(lastMsgAt + 3*keepAlive) < millis() ){ 62 | connected = false; 63 | return CH_DISCONNECTED; 64 | } 65 | if ( (unsigned long)(lastMsgAt + keepAlive) < millis()) 66 | return CH_NEEDS_PING; 67 | else 68 | return CH_CONNECTED; 69 | } 70 | 71 | const char* ArduCastConnection::getDestinationId(){ 72 | return destId; 73 | } 74 | 75 | bool ArduCastConnection::encode_string(pb_ostream_t *stream, const pb_field_iter_t *field, void * const *arg) 76 | { 77 | const char *str = (const char*)(*arg); 78 | 79 | // Serial.printf("Encode: %s (%d)\n",str, strlen(str)); 80 | if (!pb_encode_tag_for_field(stream, field)) 81 | return false; 82 | 83 | return pb_encode_string(stream, (uint8_t*)str, strlen(str)); 84 | } 85 | 86 | int ArduCastConnection::writeMsg(const char* nameSpace, const char* payload){ 87 | if ( !client.connected() ) 88 | return -1; 89 | 90 | extensions_api_cast_channel_CastMessage newMsg = extensions_api_cast_channel_CastMessage_init_zero; 91 | pb_ostream_t stream = pb_ostream_from_buffer(writeBuffer+4, writeBufferSize-4); 92 | newMsg.source_id.arg = (void*)CC_SOURCEID; 93 | newMsg.source_id.funcs.encode = encode_string; 94 | newMsg.destination_id.arg = (void*)destId; 95 | newMsg.destination_id.funcs.encode = encode_string; 96 | newMsg.namespace_fix.arg = (void*)nameSpace; 97 | newMsg.namespace_fix.funcs.encode = encode_string; 98 | newMsg.payload_type = extensions_api_cast_channel_CastMessage_PayloadType_STRING; 99 | newMsg.payload_utf8.arg = (void*)payload; 100 | newMsg.payload_utf8.funcs.encode = encode_string; 101 | 102 | bool status = pb_encode(&stream, extensions_api_cast_channel_CastMessage_fields, &newMsg); 103 | if (!status) 104 | return -2; 105 | 106 | writeBuffer[0] = (stream.bytes_written>>24) & 0xFF; 107 | writeBuffer[1] = (stream.bytes_written>>16) & 0xFF; 108 | writeBuffer[2] = (stream.bytes_written>>8) & 0xFF; 109 | writeBuffer[3] = (stream.bytes_written>>0) & 0xFF; 110 | 111 | uint32_t len = client.write(writeBuffer, stream.bytes_written+4); 112 | if (len < stream.bytes_written+4) 113 | return -3; 114 | 115 | return 0; 116 | } 117 | 118 | 119 | //////////////////////// 120 | 121 | 122 | uint32_t ArduCastControl::getIncomingMessageLength(WiFiClientSecure &client){ 123 | uint8_t buffer[4]; 124 | client.peekBytes(buffer, 4); 125 | uint32_t len = (buffer[0]<<24) + (buffer[1]<<16) + (buffer[2]<<8) + buffer[3]; 126 | return len; 127 | } 128 | 129 | 130 | uint32_t ArduCastControl::getRawMessage(uint8_t *buffer, uint16_t bufSize, WiFiClientSecure &client, uint32_t timeout){ 131 | unsigned long start = millis(); 132 | // Serial.printf("Checking read %d\n", client.available()); 133 | if (bufSize <= 4 || client.available() < 4 ){ 134 | return 0; 135 | } 136 | 137 | uint16_t len = getIncomingMessageLength(client); 138 | while ( client.available() < len+4 ){ 139 | if (millis() - start > timeout ){ 140 | // Serial.println("timeout"); 141 | while(client.available()) 142 | client.read(); 143 | return 0; 144 | } 145 | } 146 | 147 | uint32_t dump = 0; 148 | if ( bufSize < len + 4 ){ 149 | dump = len - (bufSize-4); 150 | len = bufSize-4; 151 | } 152 | client.readBytes(buffer, len+4); 153 | while ( dump > 0){ 154 | client.read(); 155 | dump--; 156 | } 157 | return len+4; 158 | } 159 | 160 | 161 | 162 | int ArduCastControl::connect(const char* host){ 163 | client.allowSelfSignedCerts(); //chromecast seems to use self signed cert 164 | 165 | int err = client.connect(host, 8009); 166 | if ( !err ){ 167 | return -10; 168 | } 169 | 170 | connectionStatus = TCPALIVE; 171 | 172 | // deviceConnection.init(client, PING_TIMEOUT, connBuffer, CONNBUFFER_SIZE); 173 | // applicationConnection.init(client, PING_TIMEOUT, connBuffer, CONNBUFFER_SIZE); 174 | err = deviceConnection.connect(CC_MAIN_DESTIID); 175 | if ( err == 0 ) 176 | connectionStatus = CONNECTED; 177 | return err; 178 | } 179 | 180 | void ArduCastControl::printRawMsg(int64_t len, uint8_t *buffer){ 181 | 182 | Serial.printf("Message Length: %lld\n", len); 183 | 184 | Serial.print("Message: "); 185 | 186 | //python style print for compare 187 | for(int64_t i = 0; i 31){ 189 | Serial.printf("%c",buffer[i]); 190 | } else { 191 | Serial.printf("\\x%02X",buffer[i]); 192 | } 193 | } 194 | //hex stream print for https://protogen.marcgravell.com/decode 195 | // for(int64_t i = 0; i> 3; 215 | uint8_t processedBytes = 1; 216 | //Serial.printf("desc=0x%02x\n", bufferStart[0]); 217 | processedBytes += pbDecodeVarint(bufferStart+1, lengthOrValue); 218 | return processedBytes; 219 | } 220 | 221 | 222 | connection_t ArduCastControl::loop(){ 223 | if ( !client.connected() ){ 224 | client.stopAll(); 225 | connectionStatus = DISCONNECTED; 226 | return DISCONNECTED; 227 | } 228 | uint32_t read; 229 | bool rxProcessed = false; 230 | 231 | //--------------------- RX code ----------------------------- 232 | do { 233 | //download the msg to connBuffer (only accept what we expect) 234 | read = getRawMessage(connBuffer, CONNBUFFER_SIZE, client, 100); 235 | if ( read > 0){ 236 | rxProcessed = true; //this will disable tx operations in this loop 237 | msgSent = false; //we assume this is a response to the message we sent 238 | errorCount = 5; //connection is alive, reset errorCount 239 | uint8_t processPayload = 0; //assume no need to process it 240 | //printRawMsg(read-4, connBuffer+4); 241 | uint32_t offset = 4; //skip the length field, it's not pb 242 | //iterate through protobuf 243 | do { 244 | uint8_t tag, wire; 245 | uint32_t lengthOrValue; 246 | 247 | offset += pbDecodeHeader(connBuffer+offset, &tag, &wire, &lengthOrValue); 248 | //check which device responded, accept it as pong 249 | if ( tag == extensions_api_cast_channel_CastMessage_source_id_tag ){ 250 | if( 0 == memcmp(connBuffer+offset, deviceConnection.getDestinationId(), lengthOrValue)) 251 | { 252 | //main device, process the payload of main RECEIVER_STATUS 253 | // Serial.println("Pong from device"); 254 | deviceConnection.pinged(); 255 | processPayload = 1; 256 | } 257 | if ( applicationConnection.getConnectionStatus() != CH_DISCONNECTED && 258 | 0 == memcmp(connBuffer+offset, applicationConnection.getDestinationId(), lengthOrValue)) 259 | { 260 | //application, process the payload as MEDIA_STATUS 261 | // Serial.println("Pong from app"); 262 | processPayload = 2; 263 | applicationConnection.pinged(); 264 | } 265 | } 266 | //check the namespace, we're only process receiver and media 267 | if ( tag == extensions_api_cast_channel_CastMessage_namespace_fix_tag ){ 268 | if( 0 == memcmp(connBuffer+offset, CC_NS_HEARTBEAT, lengthOrValue)){ //pong message, no need to process the payload 269 | processPayload = 0; 270 | } 271 | if( 0 == memcmp(connBuffer+offset, CC_NS_CONNECTION, lengthOrValue)){ //must be a close message 272 | if ( processPayload == 1 ){ 273 | applicationConnection.setDisconnect(); 274 | deviceConnection.setDisconnect(); 275 | connectionStatus = TCPALIVE; 276 | processPayload = false; 277 | } else if ( processPayload == 2) { 278 | applicationConnection.setDisconnect(); 279 | processPayload = false; 280 | } 281 | } 282 | } 283 | 284 | if ( processPayload > 0 && tag == extensions_api_cast_channel_CastMessage_payload_utf8_tag ){ 285 | DynamicJsonDocument doc(JSONBUFFER_SIZE); 286 | DeserializationError error = deserializeJson(doc, connBuffer+offset, lengthOrValue); 287 | if ( !error && doc.containsKey("type") && doc.containsKey("status") ){ //it pretty much must contain it 288 | if ( processPayload == 1 && strcmp("RECEIVER_STATUS", doc["type"].as()) == 0 ) { 289 | //save the generic info 290 | if ( doc["status"].containsKey("volume") ){ 291 | if( doc["status"]["volume"].containsKey("level")){ 292 | volume = doc["status"]["volume"]["level"]; 293 | } else 294 | volume = -1.0; 295 | 296 | if( doc["status"]["volume"].containsKey("muted")) 297 | isMuted = doc["status"]["volume"]["muted"].as(); 298 | else 299 | isMuted = false; 300 | 301 | } else { 302 | volume = -1.0; 303 | isMuted = false; 304 | } 305 | if ( doc["status"].containsKey("applications") ){ 306 | if ( doc["status"]["applications"][0].containsKey("sessionId") ){ 307 | strncpy(sessionId, doc["status"]["applications"][0]["sessionId"].as() ,sizeof(sessionId)); 308 | sessionId[sizeof(sessionId)-1] = '\n'; 309 | connectionStatus = CONNECT_TO_APPLICATION; 310 | } else 311 | sessionId[0] = '\n'; 312 | if ( doc["status"]["applications"][0].containsKey("statusText") ){ 313 | strncpy(statusText, doc["status"]["applications"][0]["statusText"].as() ,sizeof(statusText)); 314 | statusText[sizeof(statusText)-1] = '\n'; 315 | } else 316 | statusText[0] = '\n'; 317 | if ( doc["status"]["applications"][0].containsKey("displayName") ){ 318 | strncpy(displayName, doc["status"]["applications"][0]["displayName"].as() ,sizeof(displayName)); 319 | displayName[sizeof(displayName)-1] = '\n'; 320 | } else 321 | displayName[0] = '\n'; 322 | } else { 323 | sessionId[0] = '\0'; 324 | statusText[0] = '\0'; 325 | displayName[0] = '\0'; 326 | } 327 | } 328 | if ( processPayload == 2 && strcmp("MEDIA_STATUS", doc["type"].as()) == 0 ) { 329 | if ( doc["status"][0].containsKey("mediaSessionId") ) 330 | mediaSessionId = doc["status"][0]["mediaSessionId"]; 331 | else 332 | mediaSessionId = -1; 333 | 334 | if ( doc["status"][0].containsKey("currentTime") ) 335 | currentTime = doc["status"][0]["currentTime"]; 336 | else 337 | currentTime = 0.0; 338 | 339 | if ( doc["status"][0].containsKey("playerState") ){ 340 | if ( strcmp("IDLE", doc["status"][0]["playerState"].as()) == 0 ){ 341 | playerState = IDLE; 342 | } else if ( strcmp("BUFFERING", doc["status"][0]["playerState"].as()) == 0 ){ 343 | playerState = BUFFERING; 344 | } else if ( strcmp("PLAYING", doc["status"][0]["playerState"].as()) == 0 ){ 345 | playerState = PLAYING; 346 | } else if ( strcmp("PAUSED", doc["status"][0]["playerState"].as()) == 0 ){ 347 | playerState = PAUSED; 348 | } else { 349 | playerState = IDLE; 350 | } 351 | } else 352 | playerState = IDLE; 353 | 354 | if ( doc["status"][0].containsKey("media")){ 355 | if ( doc["status"][0]["media"].containsKey("duration") ){ 356 | duration = doc["status"][0]["media"]["duration"]; 357 | } else { 358 | duration = 0.0; 359 | } 360 | if ( doc["status"][0]["media"].containsKey("metadata") ){ 361 | if ( doc["status"][0]["media"]["metadata"].containsKey("title") ){ 362 | strncpy(title, doc["status"][0]["media"]["metadata"]["title"].as() ,sizeof(title)); 363 | title[sizeof(title)-1] = '\n'; 364 | } else { 365 | title[0] = '\0'; 366 | } 367 | if ( doc["status"][0]["media"]["metadata"].containsKey("artist") ){ 368 | strncpy(artist, doc["status"][0]["media"]["metadata"]["artist"].as() ,sizeof(artist)); 369 | artist[sizeof(artist)-1] = '\n'; 370 | } else { 371 | artist[0] = '\0'; 372 | } 373 | } else { 374 | title[0] = '\n'; 375 | artist[0] = '\n'; 376 | } 377 | } else { 378 | //CC seems to skip sending this when it's busy, so we ignore the error 379 | // duration = 0.0; 380 | // title[0] = '\n'; 381 | // artist[0] = '\n'; 382 | } 383 | } 384 | } 385 | // serializeJsonPretty(doc, Serial); 386 | // Serial.println(); 387 | } 388 | 389 | 390 | 391 | 392 | //DEBUG code -- this does not work when DynamicJson is used, probably not enough heap 393 | // Serial.printf("T:%d, W:%d", tag, wire); 394 | // if ( wire == 2 ){ 395 | // Serial.printf(" (%d): ", lengthOrValue); 396 | // Serial.printf("%.*s\n", lengthOrValue, connBuffer+offset); 397 | // } else { 398 | // Serial.printf(": %d\n", lengthOrValue); 399 | // } 400 | //end of debug code 401 | 402 | 403 | //for length delimited stuff, add the decoded length to the offset 404 | if ( wire == 2) 405 | offset+=lengthOrValue; 406 | } while(offset 0); 409 | 410 | // ---------------- TX code ------------------------ 411 | //don't send msg if we just received one; wait 500ms for an answer 412 | if ( !rxProcessed && (!msgSent || ((millis() - msgSentAt) > 500 ))){ 413 | //handle broken links 414 | if ( msgSent ){ 415 | Serial.printf("EC:%d\n", errorCount); 416 | if (--errorCount == 0 ){ 417 | client.stopAll(); 418 | connectionStatus = DISCONNECTED; 419 | msgSent = false; 420 | return DISCONNECTED; 421 | } 422 | } 423 | 424 | // Serial.println("Preparing for msg"); 425 | msgSent = false; 426 | int err = 0; 427 | if ( connectionStatus == CONNECT_TO_APPLICATION ){ 428 | // Serial.print("CA"); 429 | err = applicationConnection.connect(sessionId); 430 | if ( err == 0 ) 431 | connectionStatus = CONNECTED; 432 | } else if ( applicationConnection.getConnectionStatus() == CH_DISCONNECTED ){ 433 | // Serial.print("GS"); 434 | err = deviceConnection.writeMsg(CC_NS_RECEIVER, CC_MSG_GET_STATUS); 435 | if ( err == 0 ) { 436 | msgSentAt = millis(); 437 | msgSent = true; 438 | } 439 | } else if ( deviceConnection.getConnectionStatus() == CH_NEEDS_PING ){ 440 | // Serial.print("ping main"); 441 | err = deviceConnection.writeMsg(CC_NS_HEARTBEAT, CC_MSG_PING); 442 | if ( err == 0 ) { 443 | msgSentAt = millis(); 444 | msgSent = true; 445 | } 446 | } else if ( applicationConnection.getConnectionStatus() == CH_CONNECTED ){ 447 | // Serial.print("GSA"); 448 | err = applicationConnection.writeMsg(CC_NS_MEDIA, CC_MSG_GET_STATUS); 449 | if ( err == 0 ) { 450 | msgSentAt = millis(); 451 | msgSent = true; 452 | } 453 | } else if ( applicationConnection.getConnectionStatus() == CH_NEEDS_PING ){ //this will never happen, unless loop is called rarely 454 | // Serial.print("ping app"); 455 | err = applicationConnection.writeMsg(CC_NS_HEARTBEAT, CC_MSG_PING); 456 | if ( err == 0 ) { 457 | msgSentAt = millis(); 458 | msgSent = true; 459 | } 460 | } 461 | // if (msgSent ){ 462 | // Serial.printf("Res: %d\n", err); 463 | // } 464 | } 465 | 466 | return getConnection(); 467 | } 468 | 469 | connection_t ArduCastControl::getConnection(){ 470 | if ( msgSent ) 471 | return WAIT_FOR_RESPONSE; 472 | if ( applicationConnection.getConnectionStatus() != CH_DISCONNECTED ) 473 | return APPLICATION_RUNNING; 474 | 475 | return connectionStatus; 476 | } 477 | 478 | void ArduCastControl::dumpStatus(){ 479 | if ( getConnection() != DISCONNECTED && getConnection() != TCPALIVE ){ 480 | Serial.printf("V:%f%c\n", volume, isMuted?'M':' '); 481 | if ( applicationConnection.getConnectionStatus() != CH_DISCONNECTED ){ 482 | Serial.printf("D:%s\n", displayName); 483 | Serial.printf("S:%s\n", statusText); 484 | Serial.printf("A/T:%s/%s\n", artist, title); 485 | Serial.printf("S:%d %f/%f\n", playerState, duration, currentTime); 486 | } 487 | } 488 | } 489 | 490 | 491 | 492 | int ArduCastControl::play(){ 493 | if ( msgSent ) 494 | return -10; 495 | if ( mediaSessionId < 0 ) 496 | return -9; 497 | 498 | snprintf(cc_msg_ctrl, sizeof(cc_msg_ctrl), "%s%d}", CC_MSG_PLAY, mediaSessionId ); 499 | return applicationConnection.writeMsg(CC_NS_MEDIA, cc_msg_ctrl); 500 | } 501 | 502 | int ArduCastControl::pause(bool toggle){ 503 | if ( msgSent ) 504 | return -10; 505 | if ( mediaSessionId < 0 ) 506 | return -9; 507 | 508 | if ( toggle && playerState == PAUSED ) 509 | return play(); 510 | else { 511 | snprintf(cc_msg_ctrl, sizeof(cc_msg_ctrl), "%s%d}", CC_MSG_PAUSE, mediaSessionId ); 512 | return applicationConnection.writeMsg(CC_NS_MEDIA, cc_msg_ctrl); 513 | } 514 | } 515 | 516 | int ArduCastControl::prev(){ 517 | if ( msgSent ) 518 | return -10; 519 | if ( mediaSessionId < 0 ) 520 | return -9; 521 | 522 | snprintf(cc_msg_ctrl, sizeof(cc_msg_ctrl), "%s%d}", CC_MSG_PREV, mediaSessionId ); 523 | return applicationConnection.writeMsg(CC_NS_MEDIA, cc_msg_ctrl); 524 | } 525 | 526 | int ArduCastControl::next(){ 527 | if ( msgSent ) 528 | return -10; 529 | if ( mediaSessionId < 0 ) 530 | return -9; 531 | 532 | snprintf(cc_msg_ctrl, sizeof(cc_msg_ctrl), "%s%d}", CC_MSG_NEXT, mediaSessionId ); 533 | return applicationConnection.writeMsg(CC_NS_MEDIA, cc_msg_ctrl); 534 | } 535 | 536 | int ArduCastControl::seek(bool relative, float seekTo){ 537 | if ( msgSent ) 538 | return -10; 539 | if ( mediaSessionId < 0 ) 540 | return -9; 541 | 542 | if ( relative ) 543 | seekTo += currentTime; 544 | 545 | if ( seekTo < 0 ) 546 | seekTo = 0; 547 | if ( seekTo > duration ) 548 | seekTo = duration; 549 | 550 | snprintf(cc_msg_ctrl, sizeof(cc_msg_ctrl), "{\"type\": \"SEEK\", \"requestId\": 2, \"mediaSessionId\": %d, \"currentTime\": %f}", mediaSessionId, seekTo); 551 | return applicationConnection.writeMsg(CC_NS_MEDIA, cc_msg_ctrl); 552 | } 553 | 554 | int ArduCastControl::setVolume(bool relative, float volumeTo){ 555 | if ( msgSent ) 556 | return -10; 557 | 558 | if ( relative ) 559 | volumeTo += volume; 560 | 561 | if ( volumeTo < 0 ) 562 | volumeTo = 0; 563 | if ( volumeTo > 1 ) 564 | volumeTo = 1; 565 | 566 | snprintf(cc_msg_ctrl, sizeof(cc_msg_ctrl), "%s%f}}", CC_MSG_SET_VOL, volumeTo ); 567 | return deviceConnection.writeMsg(CC_NS_RECEIVER, cc_msg_ctrl); 568 | } 569 | 570 | int ArduCastControl::setMute(bool newMute, bool toggle){ 571 | if ( msgSent ) 572 | return -10; 573 | 574 | if ( toggle ) 575 | newMute = !isMuted; 576 | 577 | snprintf(cc_msg_ctrl, sizeof(cc_msg_ctrl), "%s%s}}", CC_MSG_VOL_MUTE, newMute?"true":"false" ); 578 | return deviceConnection.writeMsg(CC_NS_RECEIVER, cc_msg_ctrl); 579 | 580 | } 581 | -------------------------------------------------------------------------------- /ArduCastControl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * ArduCastControl.h - Arduino library to control Chromecast 3 | * Created by Andras Biro, November 1, 2020 4 | * https://github.com/andrasbiro/chromecastcontrol 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "pb.h" 12 | 13 | 14 | /** 15 | * Buffer size for JSON deconding used with ArduinoJson's dynamic allocation. 16 | * Allocated from heap. 17 | */ 18 | #ifndef JSONBUFFER_SIZE 19 | #define JSONBUFFER_SIZE 4096 20 | #endif 21 | 22 | /** 23 | * Common buffer used for both write and read a single protocolbuffer message. 24 | * Allocated with the class. 25 | * Biggest write is about 300B (seek), read can be much bigger. Maximum seems 26 | * to be about 2k, this is set to 4k for future proofing. 27 | */ 28 | #ifndef CONNBUFFER_SIZE 29 | #define CONNBUFFER_SIZE 4096 30 | #endif 31 | 32 | /** 33 | * Timeout for ping. If there was no received message for this amount of time 34 | * on a given channel, a PING message will be sent. 35 | */ 36 | #ifndef PING_TIMEOUT 37 | #define PING_TIMEOUT 5000 38 | #endif 39 | 40 | 41 | 42 | /** 43 | * Possible connection status for \ref ArduCastConnection 44 | */ 45 | typedef enum channelConnection_t{ 46 | CH_DISCONNECTED, ///< Disconnected. Either the TCP channel or the application 47 | CH_NEEDS_PING, ///< Timeout reached and a PING message should be sent. After successful PONG, \ref pinged() should be called to reset this state 48 | CH_CONNECTED, ///< Connected. Both TCP and application layer. 49 | }channelConnection_t; 50 | 51 | /** 52 | * Class to maintain a chromecast connection channel. A typicial application 53 | * needs two: 54 | * One for the device and one for the application (casting) running on the 55 | * device. 56 | * 57 | * Maintains a bare minimum for the connection. Doesn't write or read the 58 | * channel, but maintains timer for ping, holds a destination ID, 59 | * and provides a simpler function to write a protocol buffer message. 60 | * 61 | * Typcially this is not needed from the application, only from 62 | * \ref ArduCastControl. 63 | */ 64 | class ArduCastConnection { 65 | private: 66 | WiFiClientSecure& client; 67 | const int keepAlive; 68 | uint8_t *const writeBuffer; 69 | const int writeBufferSize; 70 | 71 | channelConnection_t connectionStatus = CH_DISCONNECTED; 72 | char destId[50]; 73 | unsigned long lastMsgAt = 0; 74 | bool connected = false; 75 | 76 | /** 77 | * Encoder function required for protocol buffer encoding 78 | */ 79 | static bool encode_string(pb_ostream_t *stream, const pb_field_iter_t *field, void * const *arg); 80 | public: 81 | /** 82 | * Constructor 83 | * \param[in] _client 84 | * Reference of already connected secure TCP client. Shared between 85 | * multiple classes 86 | * \param[in] _keepAlive 87 | * Timeout ater CH_NEEDS_PING is set 88 | * \param[in] _writeBuffer 89 | * Buffer to use by \ref writeMsg(). Shared between multiple classes 90 | * \param[in] _writeBufferSize 91 | * Size of \ref _writeBuffer 92 | */ 93 | ArduCastConnection(WiFiClientSecure &_client, int _keepAlive, uint8_t *_writeBuffer, int _writeBufferSize) 94 | : client(_client), keepAlive(_keepAlive), writeBuffer(_writeBuffer), writeBufferSize(_writeBufferSize) 95 | {}; 96 | 97 | /** 98 | * Connect to an application level channel. This will write a CONNECT 99 | * message to the TCP channel. 100 | * \param[in] destinationId 101 | * Destination to connect. This will be stored and used for every 102 | * subsecvent \ref writeMsg() as destination. 103 | * \return 104 | * 0 on success, -1 if TCP channel is not open, -2 if protobuf encoding 105 | * failed, -3 if TCP channel didn't accept the whole message 106 | */ 107 | int connect(const char* destinationId); 108 | 109 | /** 110 | * Resets \ref CH_NEEDS_PING status. Should be called if a message is 111 | * received on this channel. 112 | */ 113 | void pinged(); 114 | 115 | /** 116 | * Sets the status of the channel to \ref CH_DISCONNECT 117 | * Should be called e.g. if DISCONNECT message was received. 118 | */ 119 | void setDisconnect(); 120 | 121 | /** 122 | * Returns the current connection status of this channel 123 | * \return 124 | * The current connection status. 125 | */ 126 | channelConnection_t getConnectionStatus(); 127 | 128 | /** 129 | * Returns the destination ID of this channel 130 | * \return 131 | * Pointer to the destination ID string. 132 | */ 133 | const char* getDestinationId(); 134 | 135 | /** 136 | * Writes a message to this channel, to the stored destination ID 137 | * \param[in] nameSpace 138 | * The namespace to write, e.g. urn:x-cast:com.google.cast.receiver 139 | * \param[in] payload 140 | * The payload to write 141 | * \return 142 | * 0 on success, -1 if TCP channel is not open, -2 if protobuf encoding 143 | * failed, -3 if TCP channel didn't accept the whole message 144 | */ 145 | int writeMsg(const char* nameSpace, const char* payload); 146 | }; 147 | 148 | 149 | /** 150 | * Possible connection status for \ref ArduCastControl 151 | */ 152 | typedef enum connection_t{ 153 | DISCONNECTED, ///< Disconnected. TCP channel is not open 154 | TCPALIVE, ///< TCP is connected, but the application layer connection is not alive 155 | CONNECTED, ///< Both TCP and application layer is connected. No application is running on Chromecast 156 | APPLICATION_RUNNING, ///< Application is running on the chromecase (i.e. something is casting) 157 | WAIT_FOR_RESPONSE, ///< A message was sent and the response should be polled soon with a call to \ref loop() 158 | CONNECT_TO_APPLICATION, ///< Application is running, but connection is not yet established to it. \ref loop() should be called to connect 159 | } connection_t; 160 | 161 | /** 162 | * Possible values for \ref playerState 163 | * See https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.PlayerState 164 | */ 165 | typedef enum playerState_t{ 166 | IDLE, ///< No media is loaded into the player. 167 | PLAYING, ///< The media is playing. 168 | PAUSED, ///< The media is not playing. 169 | BUFFERING, ///< Player is in PLAY mode but not actively playing content. currentTime will not change. 170 | } playerState_t; 171 | 172 | /** 173 | * Main class. This class can be used to connect to a chromecast device, 174 | * poll information from it, like what is currently cast to it and control 175 | * the playback/volume on it. 176 | */ 177 | class ArduCastControl { 178 | private: 179 | uint8_t connBuffer[CONNBUFFER_SIZE]; 180 | 181 | connection_t connectionStatus = DISCONNECTED; 182 | char sessionId[50]; 183 | int32_t mediaSessionId; 184 | WiFiClientSecure client; 185 | uint8_t errorCount = 5; 186 | 187 | //IPAddress ccAddress = IPAddress(192, 168, 1, 12);//FIXME 188 | 189 | /** 190 | * Channel connection to the chromecast device itself (receiver-0) 191 | */ 192 | ArduCastConnection deviceConnection = ArduCastConnection(client, PING_TIMEOUT, connBuffer, CONNBUFFER_SIZE); 193 | 194 | /** 195 | * Channel connection to the application running on chromecast, if any. 196 | */ 197 | ArduCastConnection applicationConnection = ArduCastConnection(client, PING_TIMEOUT, connBuffer, CONNBUFFER_SIZE); 198 | 199 | /** 200 | * Downloads a message from the TCP channel. Chromecast messages start with 201 | * the length coded in 4 bytes, this function will download based on that. 202 | * The length field will be included in the downloaded message. 203 | * 204 | * \param[out] buffer 205 | * The buffer where the message will be written 206 | * \param[in] bufSize 207 | * Size of the buffer 208 | * \param[in] client 209 | * Reference to the client which should be a connected secure TCP client 210 | * \param[in] timeout 211 | * Timeout in ms. If a message can't be downloaded in this time, the 212 | * client will be purged for remaining data and the function returns. 213 | * \return 214 | * The amount of data read in bytes. 0 on timeout or if there's no data 215 | * to read. 216 | */ 217 | uint32_t getRawMessage(uint8_t *buffer, uint16_t bufSize, WiFiClientSecure &client, uint32_t timeout); 218 | 219 | /** 220 | * Helper function for \ref getRawMessage() to decode the length field of the 221 | * message. Does not read from the channel, it uses peek() functions. 222 | * 223 | * \param[in] client 224 | * Reference to the client which should be a connected secure TCP client, 225 | * with at least 4 bytes available to read. 226 | * \return 227 | * The length of the message on \ref client. 228 | */ 229 | uint32_t getIncomingMessageLength(WiFiClientSecure &client); 230 | 231 | /** 232 | * Debug function. Prints a protocol buffer message similarly how python 233 | * prints bytelists. 234 | * \param[in] len 235 | * The length of the message in bytes. 236 | * \param[in] buffer 237 | * The buffer which holds the message. Note that the length field 238 | * (first 4 bytes) in chromecast messages are not part of the protocol 239 | * buffer message, and it shouldn't be passed here. 240 | */ 241 | void printRawMsg(int64_t len, uint8_t *buffer); 242 | 243 | uint8_t pbDecodeVarint(uint8_t *bufferStart, uint32_t *decodedInt); 244 | 245 | /** 246 | * This is a very limited protobuf decoder, especially designed for 247 | * chromecast's cast_channel messages. It supports unsigned varints up to 248 | * 32 bits, in which case the value is returned in \ref lengthOrValue. 249 | * It also supports length-delimited headers (e.g. strings), in which case 250 | * \ref lengthOrValue is the length. In this case, the sting/bytestream 251 | * is not processed, but it can be easily accessed by 252 | * \ref bufferStart + ret. 253 | * 254 | * \param[in] bufferStart 255 | * The buffer where processing should start. This should point to a 256 | * protocol buffer header. 257 | * \param[out] tag 258 | * The tag decoded from the protocol buffer header (i.e. the argument's 259 | * number in the ordered list) 260 | * \param[out] wire 261 | * The wire decoded from the protocol buffer header. Should be either 262 | * 0 (varint) or 2 (length-delimited). Otherwise the processing probably 263 | * failed 264 | * \param[out] lengthOrValue 265 | * The decoded value for varint (\ref wire is 0) or the length of the 266 | * length-delimited type's length (\ref wire is 2) 267 | * \return 268 | * The number of bytes processed 269 | */ 270 | uint8_t pbDecodeHeader(uint8_t *bufferStart, uint8_t *tag, uint8_t *wire, uint32_t *lengthOrValue); 271 | 272 | unsigned long msgSentAt; 273 | bool msgSent; 274 | 275 | public: 276 | //stuff reported by chromecast's main channel 277 | 278 | /** 279 | * displayName reported by chromecast or "" if nothing is reported. 280 | * Note that this is an UTF8 string 281 | * E.g. "Spotify" 282 | */ 283 | char displayName[50]; 284 | 285 | /** 286 | * statusText reported by chromecast or "" if nothing is reported. 287 | * Note that this is an UTF8 string 288 | * E.g. "Casting: " 289 | */ 290 | char statusText[50]; 291 | 292 | /** 293 | * Volume reported by chromecast or -1 if nothing is reported 294 | * Should be between 0 and 1. 295 | */ 296 | float volume; //0-1, -1 if nothing is reported 297 | 298 | /** 299 | * True if chromecast reported muted status, false otherwise 300 | */ 301 | bool isMuted; 302 | 303 | //only valid when application is running, otherwise not even cleared 304 | 305 | /** 306 | * playerState reported by the application or IDLE when nothing is reported 307 | * E.g. PLAYING 308 | */ 309 | playerState_t playerState; 310 | 311 | /** 312 | * Duration of the song currently playing (if any) in seconds or 0 313 | * if nothing is reported 314 | */ 315 | float duration; 316 | 317 | /** 318 | * Current time in the song currently playing (if any) in seconds or 0 319 | * if nothing is reported 320 | */ 321 | float currentTime; 322 | 323 | /** 324 | * Title of song currently playing or "" if nothing is reported. 325 | * Note that this is an UTF8 string 326 | */ 327 | char title[50]; 328 | 329 | /** 330 | * Artist of song currently playing or "" if nothing is reported. 331 | * Note that this is an UTF8 string 332 | */ 333 | char artist[50]; 334 | 335 | /** 336 | * Constructor 337 | */ 338 | ArduCastControl(){} 339 | 340 | /** 341 | * Connect to chromecast. First connects to the TCP/TLS port with 342 | * self-signed certificates allowed, then connects to the main channel 343 | * of the chromecast application layer. 344 | * 345 | * \param[in] host 346 | * Host of the device to connect. 347 | * 348 | * \return 349 | * 0 on success, -1 if TCP channel is not open, -2 if protobuf encoding 350 | * failed, -3 if TCP channel didn't accept the whole message, -10 if 351 | * the TCP/TLS channel can't be opened. 352 | */ 353 | int connect(const char* host); 354 | 355 | /** 356 | * Returns the current connection status 357 | * 358 | * \return 359 | * The current connection status 360 | */ 361 | connection_t getConnection(); 362 | 363 | /** 364 | * Loop function, intended to be called periodically. 365 | * \li First checks if the connection is alive and returns \ref DISCONNECTED 366 | * if not. 367 | * \li Then checks and downlads all messages available on the TCP/TLS channel, 368 | * set ping status of application channels, handles disconnect requests 369 | * and updates status variables (e.g. \ref volume or \ref title). 370 | * If there was anything read, the function returns. 371 | * \li If notheing was read the function continous with writing a message. 372 | * It only writes, if nothing was written in the last 500ms where an 373 | * answer is expected. It writes a single message in the following order 374 | * of priority: 375 | * 1: Connect to application if status is \ref CONNECT_TO_APPLICATION 376 | * 2: Get status from main channel if no application is running 377 | * 3: Ping on the main channel if needed 378 | * 4: Get status from the application if it's running 379 | * 5: Ping the application channel if needed (which shouldn't happen due to 4) 380 | * \return 381 | * The current connection status, at the end of the loop function. 382 | */ 383 | connection_t loop(); 384 | 385 | /** 386 | * Dumps the recorded status values to Serial in the following format: 387 | * "V:<volume><muted>" 388 | * "D:<displayName>" 389 | * "S:<statusText>" 390 | * "A/T:<artist>/<title>" 391 | * "S:<playerState> <duration>:<currentSeek>" 392 | * 393 | * When no application is running, only the volume line is printed. 394 | * <volume> is float, <muted> is M when muted, nothing otherwise. 395 | * Strings are printed as is, UTF8 special characters included 396 | * <playerState> is printed as int, e.g. 2 is PAUSED 397 | * <duration> and <currentSeek> are both float in seconds. 398 | */ 399 | void dumpStatus(); 400 | 401 | /** 402 | * Play command (e.g. to resume paused playback) 403 | * 404 | * \return 405 | * 0 on success, -1 if TCP channel is not open, -2 if protobuf encoding 406 | * failed, -3 if TCP channel didn't accept the whole message, -10 if 407 | * system is waiting for a response and -9 if the current media 408 | * can't be identified (e.g. media was changed) 409 | */ 410 | int play(); 411 | 412 | /** 413 | * Pause or resume playback. 414 | * \param[in] toggle 415 | * If false, the function will send a PAUSE command 416 | * If true, the function checks the current \ref playerState and send 417 | * PAUSE if playing or PLAY if paused. 418 | * \return 419 | * 0 on success, -1 if TCP channel is not open, -2 if protobuf encoding 420 | * failed, -3 if TCP channel didn't accept the whole message, -10 if 421 | * system is waiting for a response and -9 if the current media 422 | * can't be identified (e.g. media was changed) 423 | */ 424 | int pause(bool toggle); 425 | 426 | /** 427 | * Previous command. Jumps to the beginning of track or previous track. 428 | * 429 | * \return 430 | * 0 on success, -1 if TCP channel is not open, -2 if protobuf encoding 431 | * failed, -3 if TCP channel didn't accept the whole message, -10 if 432 | * system is waiting for a response and -9 if the current media 433 | * can't be identified (e.g. media was changed) 434 | */ 435 | int prev(); 436 | 437 | /** 438 | * Next command. Jumps to the next track, 439 | * 440 | * \return 441 | * 0 on success, -1 if TCP channel is not open, -2 if protobuf encoding 442 | * failed, -3 if TCP channel didn't accept the whole message, -10 if 443 | * system is waiting for a response and -9 if the current media 444 | * can't be identified (e.g. media was changed) 445 | */ 446 | int next(); 447 | 448 | /** 449 | * Seek to the requested position in media 450 | * \param[in] relative 451 | * If false, seeks to \ref seekTo, if true, seeks to 452 | * \ref seekTo + \ref currentTime 453 | * \param[in] seekTo 454 | * Position to seek to, either in relative or absolute 455 | * 456 | * \return 457 | * 0 on success, -1 if TCP channel is not open, -2 if protobuf encoding 458 | * failed, -3 if TCP channel didn't accept the whole message, -10 if 459 | * system is waiting for a response and -9 if the current media 460 | * can't be identified (e.g. media was changed) 461 | */ 462 | int seek(bool relative, float seekTo); 463 | 464 | /** 465 | * Sets the volume 466 | * \param[in] relative 467 | * If false, sets to \ref volumeTo, if true, seeks to 468 | * \ref volumeTo + \ref volume 469 | * \param[in] volumeTo 470 | * Volume to set, either in relative or absolute 471 | * 472 | * \return 473 | * 0 on success, -1 if TCP channel is not open, -2 if protobuf encoding 474 | * failed, -3 if TCP channel didn't accept the whole message, -10 if 475 | * system is waiting for a response. 476 | */ 477 | int setVolume(bool relative, float volumeTo); 478 | 479 | /** 480 | * Sets mute/unmute 481 | * \param[in] newMute 482 | * Set it to true for mute, false for unmute. 483 | * Ignored if \ref toggle is set. 484 | * \param[in] toggle 485 | * Unmute if currently muted, mute if currently unmuted 486 | * 487 | * \return 488 | * 0 on success, -1 if TCP channel is not open, -2 if protobuf encoding 489 | * failed, -3 if TCP channel didn't accept the whole message, -10 if 490 | * system is waiting for a response. 491 | */ 492 | int setMute(bool newMute, bool toggle); 493 | 494 | }; 495 | 496 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 András Bíró 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ArduCastControl 2 | 3 | Chromecast control library for platformio/arduino. It supports requesting some 4 | information of the casting (e.g. artist and title), as well as a minimal control 5 | (pause, previous, next, seek and volume). 6 | 7 | ## Dependencies 8 | 9 | The library depends on [ArduinoJson](https://arduinojson.org/) and 10 | [nanopb](https://jpa.kapsi.fi/nanopb/). This is already set for platformio. 11 | 12 | It has a significant RAM footprint, which is usually not an issue for wifi 13 | capable boards. It was only tested on ESP8266. 14 | 15 | ## References 16 | 17 | - [Node-castv2](https://github.com/thibauts/node-castv2) has a great readme 18 | describing the protocol. 19 | - [pychromecast](https://github.com/home-assistant-libs/pychromecast) was used 20 | as a reference when implementing this 21 | - [CastVolumeKnob](https://github.com/WebmasterTD/CastVolumeKnob/) is a similar 22 | project in micropython, but it only targets volume control. 23 | - [Google's documentation on protocol 24 | buffers](https://developers.google.com/protocol-buffers/docs/encoding) 25 | 26 | ## Flow of connection 27 | 28 | Understanding how the CASTV2/chromecast protocols work took me a while, so 29 | here's a quick summary on what this library does. 30 | 31 | 1. TCP/TLS connection is opened to the device. Self-signed certificates are 32 | sufficient 33 | 2. An application layer connection to the device itself ("receiver-0") is 34 | established 35 | 3. Status is requested from the device with GET_STATUS messages on the receiver 36 | namespace 37 | 1. Reported status include volume information 38 | 4. When something casts to the device, chromecast reports an application is 39 | running with a sessionId 40 | 5. An application layer connection to the application (sessionid) is established 41 | 6. Status is requested from the application with GET_STATUS messages on the 42 | media namespace 43 | 1. Reported status includes all sort of information of the currently playing 44 | track 45 | 2. As well as a mediaSessionId, which is changed with every track change 46 | 7. Control messages can be sent to the application on the media namespace using 47 | a specified mediaSessionId 48 | 49 | ## Useful values saved 50 | 51 | All of the following are public fields of the class, which are updated when the 52 | class' loop() function is called on a given status. 53 | 54 | - **volume** - The volume set on the device, between 0 and 1 55 | - **isMuted** - True if the device is muted 56 | - **displayName** - Typically the application casting, like "Spotify" 57 | - **statusText** - A short status, e.g. "Casting: Whole Lotta Love" 58 | - **playerState** - State of playback, e.g. "PLAYING" 59 | - **title** - Title of the current song, e.g. "Whole Lotta Love" 60 | - **artist** - Artist of the current song, e.g. "Led Zeppelin" 61 | - **duration** - Duration of the current song in seconds, e.g. 333.89 62 | - **currentTime** - Current time in the song in seconds, e.g. 2.27 63 | 64 | This list can be easily extended by saving more when processing MEDIA_STATUS or 65 | RECEIVER_STATUS. 66 | 67 | ## Control methods 68 | 69 | The following controls are accessible as methods in the class: 70 | 71 | - **play()** - Plays (resumes) the current song 72 | - **pause()** - Pauses/Resumes the current song 73 | - **prev()** - Previous track 74 | - **next()** - Next track 75 | - **seek()** - Seeks in song 76 | - **setVolume()** - Volume control 77 | - **setMute()** - Mute control 78 | 79 | I think this covers all possible controls except casting and playlist features. 80 | However, extending it should be fairly easy, using the play() or setVolume() 81 | method as a template (for media/device commands respectively) 82 | 83 | ## Further documentation 84 | 85 | For further documentation, please refer to the comments in ArduCastControl.h and 86 | the example, which demonstrates the main features. 87 | 88 | ## Further developement 89 | 90 | Don't expect any new features/bugfixes, as I'm quite happy with the featureset 91 | as is. However, I do accept pull requests. -------------------------------------------------------------------------------- /authority_keys.pb.c: -------------------------------------------------------------------------------- 1 | /* Automatically generated nanopb constant definitions */ 2 | /* Generated by nanopb-0.4.2 */ 3 | 4 | #include "authority_keys.pb.h" 5 | #if PB_PROTO_HEADER_VERSION != 40 6 | #error Regenerate this file with the current version of nanopb generator. 7 | #endif 8 | 9 | PB_BIND(extensions_api_cast_channel_proto_AuthorityKeys, extensions_api_cast_channel_proto_AuthorityKeys, AUTO) 10 | 11 | 12 | PB_BIND(extensions_api_cast_channel_proto_AuthorityKeys_Key, extensions_api_cast_channel_proto_AuthorityKeys_Key, AUTO) 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /authority_keys.pb.h: -------------------------------------------------------------------------------- 1 | /* Automatically generated nanopb header */ 2 | /* Generated by nanopb-0.4.2 */ 3 | 4 | #ifndef PB_EXTENSIONS_API_CAST_CHANNEL_PROTO_AUTHORITY_KEYS_PB_H_INCLUDED 5 | #define PB_EXTENSIONS_API_CAST_CHANNEL_PROTO_AUTHORITY_KEYS_PB_H_INCLUDED 6 | #include <pb.h> 7 | 8 | #if PB_PROTO_HEADER_VERSION != 40 9 | #error Regenerate this file with the current version of nanopb generator. 10 | #endif 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | /* Struct definitions */ 17 | typedef struct _extensions_api_cast_channel_proto_AuthorityKeys { 18 | pb_callback_t keys; 19 | } extensions_api_cast_channel_proto_AuthorityKeys; 20 | 21 | typedef struct _extensions_api_cast_channel_proto_AuthorityKeys_Key { 22 | pb_callback_t fingerprint; 23 | pb_callback_t public_key; 24 | } extensions_api_cast_channel_proto_AuthorityKeys_Key; 25 | 26 | 27 | /* Initializer values for message structs */ 28 | #define extensions_api_cast_channel_proto_AuthorityKeys_init_default {{{NULL}, NULL}} 29 | #define extensions_api_cast_channel_proto_AuthorityKeys_Key_init_default {{{NULL}, NULL}, {{NULL}, NULL}} 30 | #define extensions_api_cast_channel_proto_AuthorityKeys_init_zero {{{NULL}, NULL}} 31 | #define extensions_api_cast_channel_proto_AuthorityKeys_Key_init_zero {{{NULL}, NULL}, {{NULL}, NULL}} 32 | 33 | /* Field tags (for use in manual encoding/decoding) */ 34 | #define extensions_api_cast_channel_proto_AuthorityKeys_keys_tag 1 35 | #define extensions_api_cast_channel_proto_AuthorityKeys_Key_fingerprint_tag 1 36 | #define extensions_api_cast_channel_proto_AuthorityKeys_Key_public_key_tag 2 37 | 38 | /* Struct field encoding specification for nanopb */ 39 | #define extensions_api_cast_channel_proto_AuthorityKeys_FIELDLIST(X, a) \ 40 | X(a, CALLBACK, REPEATED, MESSAGE, keys, 1) 41 | #define extensions_api_cast_channel_proto_AuthorityKeys_CALLBACK pb_default_field_callback 42 | #define extensions_api_cast_channel_proto_AuthorityKeys_DEFAULT NULL 43 | #define extensions_api_cast_channel_proto_AuthorityKeys_keys_MSGTYPE extensions_api_cast_channel_proto_AuthorityKeys_Key 44 | 45 | #define extensions_api_cast_channel_proto_AuthorityKeys_Key_FIELDLIST(X, a) \ 46 | X(a, CALLBACK, REQUIRED, BYTES, fingerprint, 1) \ 47 | X(a, CALLBACK, REQUIRED, BYTES, public_key, 2) 48 | #define extensions_api_cast_channel_proto_AuthorityKeys_Key_CALLBACK pb_default_field_callback 49 | #define extensions_api_cast_channel_proto_AuthorityKeys_Key_DEFAULT NULL 50 | 51 | extern const pb_msgdesc_t extensions_api_cast_channel_proto_AuthorityKeys_msg; 52 | extern const pb_msgdesc_t extensions_api_cast_channel_proto_AuthorityKeys_Key_msg; 53 | 54 | /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ 55 | #define extensions_api_cast_channel_proto_AuthorityKeys_fields &extensions_api_cast_channel_proto_AuthorityKeys_msg 56 | #define extensions_api_cast_channel_proto_AuthorityKeys_Key_fields &extensions_api_cast_channel_proto_AuthorityKeys_Key_msg 57 | 58 | /* Maximum encoded size of messages (where known) */ 59 | /* extensions_api_cast_channel_proto_AuthorityKeys_size depends on runtime parameters */ 60 | /* extensions_api_cast_channel_proto_AuthorityKeys_Key_size depends on runtime parameters */ 61 | 62 | #ifdef __cplusplus 63 | } /* extern "C" */ 64 | #endif 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /cast_channel.pb.c: -------------------------------------------------------------------------------- 1 | /* Automatically generated nanopb constant definitions */ 2 | /* Generated by nanopb-0.4.2 */ 3 | 4 | #include "cast_channel.pb.h" 5 | #if PB_PROTO_HEADER_VERSION != 40 6 | #error Regenerate this file with the current version of nanopb generator. 7 | #endif 8 | 9 | PB_BIND(extensions_api_cast_channel_CastMessage, extensions_api_cast_channel_CastMessage, AUTO) 10 | 11 | 12 | PB_BIND(extensions_api_cast_channel_AuthChallenge, extensions_api_cast_channel_AuthChallenge, AUTO) 13 | 14 | 15 | PB_BIND(extensions_api_cast_channel_AuthResponse, extensions_api_cast_channel_AuthResponse, AUTO) 16 | 17 | 18 | PB_BIND(extensions_api_cast_channel_AuthError, extensions_api_cast_channel_AuthError, AUTO) 19 | 20 | 21 | PB_BIND(extensions_api_cast_channel_DeviceAuthMessage, extensions_api_cast_channel_DeviceAuthMessage, AUTO) 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /cast_channel.pb.h: -------------------------------------------------------------------------------- 1 | /* Automatically generated nanopb header */ 2 | /* Generated by nanopb-0.4.2 */ 3 | 4 | #ifndef PB_EXTENSIONS_API_CAST_CHANNEL_CAST_CHANNEL_PB_H_INCLUDED 5 | #define PB_EXTENSIONS_API_CAST_CHANNEL_CAST_CHANNEL_PB_H_INCLUDED 6 | #include <pb.h> 7 | 8 | #if PB_PROTO_HEADER_VERSION != 40 9 | #error Regenerate this file with the current version of nanopb generator. 10 | #endif 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | /* Enum definitions */ 17 | typedef enum _extensions_api_cast_channel_SignatureAlgorithm { 18 | extensions_api_cast_channel_SignatureAlgorithm_UNSPECIFIED = 0, 19 | extensions_api_cast_channel_SignatureAlgorithm_RSASSA_PKCS1v15 = 1, 20 | extensions_api_cast_channel_SignatureAlgorithm_RSASSA_PSS = 2 21 | } extensions_api_cast_channel_SignatureAlgorithm; 22 | 23 | typedef enum _extensions_api_cast_channel_HashAlgorithm { 24 | extensions_api_cast_channel_HashAlgorithm_SHA1 = 0, 25 | extensions_api_cast_channel_HashAlgorithm_SHA256 = 1 26 | } extensions_api_cast_channel_HashAlgorithm; 27 | 28 | typedef enum _extensions_api_cast_channel_CastMessage_ProtocolVersion { 29 | extensions_api_cast_channel_CastMessage_ProtocolVersion_CASTV2_1_0 = 0 30 | } extensions_api_cast_channel_CastMessage_ProtocolVersion; 31 | 32 | typedef enum _extensions_api_cast_channel_CastMessage_PayloadType { 33 | extensions_api_cast_channel_CastMessage_PayloadType_STRING = 0, 34 | extensions_api_cast_channel_CastMessage_PayloadType_BINARY = 1 35 | } extensions_api_cast_channel_CastMessage_PayloadType; 36 | 37 | typedef enum _extensions_api_cast_channel_AuthError_ErrorType { 38 | extensions_api_cast_channel_AuthError_ErrorType_INTERNAL_ERROR = 0, 39 | extensions_api_cast_channel_AuthError_ErrorType_NO_TLS = 1, 40 | extensions_api_cast_channel_AuthError_ErrorType_SIGNATURE_ALGORITHM_UNAVAILABLE = 2 41 | } extensions_api_cast_channel_AuthError_ErrorType; 42 | 43 | /* Struct definitions */ 44 | typedef struct _extensions_api_cast_channel_AuthChallenge { 45 | bool has_signature_algorithm; 46 | extensions_api_cast_channel_SignatureAlgorithm signature_algorithm; 47 | pb_callback_t sender_nonce; 48 | bool has_hash_algorithm; 49 | extensions_api_cast_channel_HashAlgorithm hash_algorithm; 50 | } extensions_api_cast_channel_AuthChallenge; 51 | 52 | typedef struct _extensions_api_cast_channel_AuthError { 53 | extensions_api_cast_channel_AuthError_ErrorType error_type; 54 | } extensions_api_cast_channel_AuthError; 55 | 56 | typedef struct _extensions_api_cast_channel_AuthResponse { 57 | pb_callback_t signature; 58 | pb_callback_t client_auth_certificate; 59 | pb_callback_t intermediate_certificate; 60 | bool has_signature_algorithm; 61 | extensions_api_cast_channel_SignatureAlgorithm signature_algorithm; 62 | pb_callback_t sender_nonce; 63 | bool has_hash_algorithm; 64 | extensions_api_cast_channel_HashAlgorithm hash_algorithm; 65 | pb_callback_t crl; 66 | } extensions_api_cast_channel_AuthResponse; 67 | 68 | typedef struct _extensions_api_cast_channel_CastMessage { 69 | extensions_api_cast_channel_CastMessage_ProtocolVersion protocol_version; 70 | pb_callback_t source_id; 71 | pb_callback_t destination_id; 72 | pb_callback_t namespace_fix; 73 | extensions_api_cast_channel_CastMessage_PayloadType payload_type; 74 | pb_callback_t payload_utf8; 75 | pb_callback_t payload_binary; 76 | } extensions_api_cast_channel_CastMessage; 77 | 78 | typedef struct _extensions_api_cast_channel_DeviceAuthMessage { 79 | bool has_challenge; 80 | extensions_api_cast_channel_AuthChallenge challenge; 81 | bool has_response; 82 | extensions_api_cast_channel_AuthResponse response; 83 | bool has_error; 84 | extensions_api_cast_channel_AuthError error; 85 | } extensions_api_cast_channel_DeviceAuthMessage; 86 | 87 | 88 | /* Helper constants for enums */ 89 | #define _extensions_api_cast_channel_SignatureAlgorithm_MIN extensions_api_cast_channel_SignatureAlgorithm_UNSPECIFIED 90 | #define _extensions_api_cast_channel_SignatureAlgorithm_MAX extensions_api_cast_channel_SignatureAlgorithm_RSASSA_PSS 91 | #define _extensions_api_cast_channel_SignatureAlgorithm_ARRAYSIZE ((extensions_api_cast_channel_SignatureAlgorithm)(extensions_api_cast_channel_SignatureAlgorithm_RSASSA_PSS+1)) 92 | 93 | #define _extensions_api_cast_channel_HashAlgorithm_MIN extensions_api_cast_channel_HashAlgorithm_SHA1 94 | #define _extensions_api_cast_channel_HashAlgorithm_MAX extensions_api_cast_channel_HashAlgorithm_SHA256 95 | #define _extensions_api_cast_channel_HashAlgorithm_ARRAYSIZE ((extensions_api_cast_channel_HashAlgorithm)(extensions_api_cast_channel_HashAlgorithm_SHA256+1)) 96 | 97 | #define _extensions_api_cast_channel_CastMessage_ProtocolVersion_MIN extensions_api_cast_channel_CastMessage_ProtocolVersion_CASTV2_1_0 98 | #define _extensions_api_cast_channel_CastMessage_ProtocolVersion_MAX extensions_api_cast_channel_CastMessage_ProtocolVersion_CASTV2_1_0 99 | #define _extensions_api_cast_channel_CastMessage_ProtocolVersion_ARRAYSIZE ((extensions_api_cast_channel_CastMessage_ProtocolVersion)(extensions_api_cast_channel_CastMessage_ProtocolVersion_CASTV2_1_0+1)) 100 | 101 | #define _extensions_api_cast_channel_CastMessage_PayloadType_MIN extensions_api_cast_channel_CastMessage_PayloadType_STRING 102 | #define _extensions_api_cast_channel_CastMessage_PayloadType_MAX extensions_api_cast_channel_CastMessage_PayloadType_BINARY 103 | #define _extensions_api_cast_channel_CastMessage_PayloadType_ARRAYSIZE ((extensions_api_cast_channel_CastMessage_PayloadType)(extensions_api_cast_channel_CastMessage_PayloadType_BINARY+1)) 104 | 105 | #define _extensions_api_cast_channel_AuthError_ErrorType_MIN extensions_api_cast_channel_AuthError_ErrorType_INTERNAL_ERROR 106 | #define _extensions_api_cast_channel_AuthError_ErrorType_MAX extensions_api_cast_channel_AuthError_ErrorType_SIGNATURE_ALGORITHM_UNAVAILABLE 107 | #define _extensions_api_cast_channel_AuthError_ErrorType_ARRAYSIZE ((extensions_api_cast_channel_AuthError_ErrorType)(extensions_api_cast_channel_AuthError_ErrorType_SIGNATURE_ALGORITHM_UNAVAILABLE+1)) 108 | 109 | 110 | /* Initializer values for message structs */ 111 | #define extensions_api_cast_channel_CastMessage_init_default {_extensions_api_cast_channel_CastMessage_ProtocolVersion_MIN, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, _extensions_api_cast_channel_CastMessage_PayloadType_MIN, {{NULL}, NULL}, {{NULL}, NULL}} 112 | #define extensions_api_cast_channel_AuthChallenge_init_default {false, extensions_api_cast_channel_SignatureAlgorithm_RSASSA_PKCS1v15, {{NULL}, NULL}, false, extensions_api_cast_channel_HashAlgorithm_SHA1} 113 | #define extensions_api_cast_channel_AuthResponse_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, false, extensions_api_cast_channel_SignatureAlgorithm_RSASSA_PKCS1v15, {{NULL}, NULL}, false, extensions_api_cast_channel_HashAlgorithm_SHA1, {{NULL}, NULL}} 114 | #define extensions_api_cast_channel_AuthError_init_default {_extensions_api_cast_channel_AuthError_ErrorType_MIN} 115 | #define extensions_api_cast_channel_DeviceAuthMessage_init_default {false, extensions_api_cast_channel_AuthChallenge_init_default, false, extensions_api_cast_channel_AuthResponse_init_default, false, extensions_api_cast_channel_AuthError_init_default} 116 | #define extensions_api_cast_channel_CastMessage_init_zero {_extensions_api_cast_channel_CastMessage_ProtocolVersion_MIN, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, _extensions_api_cast_channel_CastMessage_PayloadType_MIN, {{NULL}, NULL}, {{NULL}, NULL}} 117 | #define extensions_api_cast_channel_AuthChallenge_init_zero {false, _extensions_api_cast_channel_SignatureAlgorithm_MIN, {{NULL}, NULL}, false, _extensions_api_cast_channel_HashAlgorithm_MIN} 118 | #define extensions_api_cast_channel_AuthResponse_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, false, _extensions_api_cast_channel_SignatureAlgorithm_MIN, {{NULL}, NULL}, false, _extensions_api_cast_channel_HashAlgorithm_MIN, {{NULL}, NULL}} 119 | #define extensions_api_cast_channel_AuthError_init_zero {_extensions_api_cast_channel_AuthError_ErrorType_MIN} 120 | #define extensions_api_cast_channel_DeviceAuthMessage_init_zero {false, extensions_api_cast_channel_AuthChallenge_init_zero, false, extensions_api_cast_channel_AuthResponse_init_zero, false, extensions_api_cast_channel_AuthError_init_zero} 121 | 122 | /* Field tags (for use in manual encoding/decoding) */ 123 | #define extensions_api_cast_channel_AuthChallenge_signature_algorithm_tag 1 124 | #define extensions_api_cast_channel_AuthChallenge_sender_nonce_tag 2 125 | #define extensions_api_cast_channel_AuthChallenge_hash_algorithm_tag 3 126 | #define extensions_api_cast_channel_AuthError_error_type_tag 1 127 | #define extensions_api_cast_channel_AuthResponse_signature_tag 1 128 | #define extensions_api_cast_channel_AuthResponse_client_auth_certificate_tag 2 129 | #define extensions_api_cast_channel_AuthResponse_intermediate_certificate_tag 3 130 | #define extensions_api_cast_channel_AuthResponse_signature_algorithm_tag 4 131 | #define extensions_api_cast_channel_AuthResponse_sender_nonce_tag 5 132 | #define extensions_api_cast_channel_AuthResponse_hash_algorithm_tag 6 133 | #define extensions_api_cast_channel_AuthResponse_crl_tag 7 134 | #define extensions_api_cast_channel_CastMessage_protocol_version_tag 1 135 | #define extensions_api_cast_channel_CastMessage_source_id_tag 2 136 | #define extensions_api_cast_channel_CastMessage_destination_id_tag 3 137 | #define extensions_api_cast_channel_CastMessage_namespace_fix_tag 4 138 | #define extensions_api_cast_channel_CastMessage_payload_type_tag 5 139 | #define extensions_api_cast_channel_CastMessage_payload_utf8_tag 6 140 | #define extensions_api_cast_channel_CastMessage_payload_binary_tag 7 141 | #define extensions_api_cast_channel_DeviceAuthMessage_challenge_tag 1 142 | #define extensions_api_cast_channel_DeviceAuthMessage_response_tag 2 143 | #define extensions_api_cast_channel_DeviceAuthMessage_error_tag 3 144 | 145 | /* Struct field encoding specification for nanopb */ 146 | #define extensions_api_cast_channel_CastMessage_FIELDLIST(X, a) \ 147 | X(a, STATIC, REQUIRED, UENUM, protocol_version, 1) \ 148 | X(a, CALLBACK, REQUIRED, STRING, source_id, 2) \ 149 | X(a, CALLBACK, REQUIRED, STRING, destination_id, 3) \ 150 | X(a, CALLBACK, REQUIRED, STRING, namespace_fix, 4) \ 151 | X(a, STATIC, REQUIRED, UENUM, payload_type, 5) \ 152 | X(a, CALLBACK, OPTIONAL, STRING, payload_utf8, 6) \ 153 | X(a, CALLBACK, OPTIONAL, BYTES, payload_binary, 7) 154 | #define extensions_api_cast_channel_CastMessage_CALLBACK pb_default_field_callback 155 | #define extensions_api_cast_channel_CastMessage_DEFAULT NULL 156 | 157 | #define extensions_api_cast_channel_AuthChallenge_FIELDLIST(X, a) \ 158 | X(a, STATIC, OPTIONAL, UENUM, signature_algorithm, 1) \ 159 | X(a, CALLBACK, OPTIONAL, BYTES, sender_nonce, 2) \ 160 | X(a, STATIC, OPTIONAL, UENUM, hash_algorithm, 3) 161 | #define extensions_api_cast_channel_AuthChallenge_CALLBACK pb_default_field_callback 162 | #define extensions_api_cast_channel_AuthChallenge_DEFAULT (const pb_byte_t*)"\x08\x01\x00" 163 | 164 | #define extensions_api_cast_channel_AuthResponse_FIELDLIST(X, a) \ 165 | X(a, CALLBACK, REQUIRED, BYTES, signature, 1) \ 166 | X(a, CALLBACK, REQUIRED, BYTES, client_auth_certificate, 2) \ 167 | X(a, CALLBACK, REPEATED, BYTES, intermediate_certificate, 3) \ 168 | X(a, STATIC, OPTIONAL, UENUM, signature_algorithm, 4) \ 169 | X(a, CALLBACK, OPTIONAL, BYTES, sender_nonce, 5) \ 170 | X(a, STATIC, OPTIONAL, UENUM, hash_algorithm, 6) \ 171 | X(a, CALLBACK, OPTIONAL, BYTES, crl, 7) 172 | #define extensions_api_cast_channel_AuthResponse_CALLBACK pb_default_field_callback 173 | #define extensions_api_cast_channel_AuthResponse_DEFAULT (const pb_byte_t*)"\x20\x01\x00" 174 | 175 | #define extensions_api_cast_channel_AuthError_FIELDLIST(X, a) \ 176 | X(a, STATIC, REQUIRED, UENUM, error_type, 1) 177 | #define extensions_api_cast_channel_AuthError_CALLBACK NULL 178 | #define extensions_api_cast_channel_AuthError_DEFAULT NULL 179 | 180 | #define extensions_api_cast_channel_DeviceAuthMessage_FIELDLIST(X, a) \ 181 | X(a, STATIC, OPTIONAL, MESSAGE, challenge, 1) \ 182 | X(a, STATIC, OPTIONAL, MESSAGE, response, 2) \ 183 | X(a, STATIC, OPTIONAL, MESSAGE, error, 3) 184 | #define extensions_api_cast_channel_DeviceAuthMessage_CALLBACK NULL 185 | #define extensions_api_cast_channel_DeviceAuthMessage_DEFAULT NULL 186 | #define extensions_api_cast_channel_DeviceAuthMessage_challenge_MSGTYPE extensions_api_cast_channel_AuthChallenge 187 | #define extensions_api_cast_channel_DeviceAuthMessage_response_MSGTYPE extensions_api_cast_channel_AuthResponse 188 | #define extensions_api_cast_channel_DeviceAuthMessage_error_MSGTYPE extensions_api_cast_channel_AuthError 189 | 190 | extern const pb_msgdesc_t extensions_api_cast_channel_CastMessage_msg; 191 | extern const pb_msgdesc_t extensions_api_cast_channel_AuthChallenge_msg; 192 | extern const pb_msgdesc_t extensions_api_cast_channel_AuthResponse_msg; 193 | extern const pb_msgdesc_t extensions_api_cast_channel_AuthError_msg; 194 | extern const pb_msgdesc_t extensions_api_cast_channel_DeviceAuthMessage_msg; 195 | 196 | /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ 197 | #define extensions_api_cast_channel_CastMessage_fields &extensions_api_cast_channel_CastMessage_msg 198 | #define extensions_api_cast_channel_AuthChallenge_fields &extensions_api_cast_channel_AuthChallenge_msg 199 | #define extensions_api_cast_channel_AuthResponse_fields &extensions_api_cast_channel_AuthResponse_msg 200 | #define extensions_api_cast_channel_AuthError_fields &extensions_api_cast_channel_AuthError_msg 201 | #define extensions_api_cast_channel_DeviceAuthMessage_fields &extensions_api_cast_channel_DeviceAuthMessage_msg 202 | 203 | /* Maximum encoded size of messages (where known) */ 204 | /* extensions_api_cast_channel_CastMessage_size depends on runtime parameters */ 205 | /* extensions_api_cast_channel_AuthChallenge_size depends on runtime parameters */ 206 | /* extensions_api_cast_channel_AuthResponse_size depends on runtime parameters */ 207 | #define extensions_api_cast_channel_AuthError_size 2 208 | /* extensions_api_cast_channel_DeviceAuthMessage_size depends on runtime parameters */ 209 | 210 | #ifdef __cplusplus 211 | } /* extern "C" */ 212 | #endif 213 | 214 | #endif 215 | -------------------------------------------------------------------------------- /examples/simpleChromecastControl/simpleChromecastControl.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * This implements a simple chromecast control example, tested on wemos D1 mini, 3 | * but should work on any ESP8266 (and probably ESP32) 4 | * Current status gathered from chromecast will be printed on serial. 5 | * Buttons on D5/D6/D7 will act as pause/prev/next respectively 6 | */ 7 | 8 | #include <Arduino.h> 9 | #include <ArduinoOTA.h> 10 | #include "ArduCastControl.h" 11 | 12 | #define B_SELECT D5 13 | #define B_LEFT D6 14 | #define B_RIGHT D7 15 | #define CHROMECASTIP "192.168.1.12" 16 | 17 | 18 | ArduCastControl cc = ArduCastControl(); 19 | bool bSelectPressed = false, bLeftPressed=false, bRightPressed=false; 20 | 21 | void setup() { 22 | 23 | Serial.begin(115200); 24 | Serial.println("booted"); 25 | 26 | ArduinoOTA.setHostname ("chromecastremote"); 27 | ArduinoOTA.begin(); 28 | 29 | pinMode(D8, OUTPUT); //common pin for keys, used for pulldown - should have a pulldown anyway 30 | pinMode(B_SELECT, INPUT_PULLUP); 31 | pinMode(B_LEFT, INPUT_PULLUP); 32 | pinMode(B_RIGHT, INPUT_PULLUP); 33 | } 34 | 35 | uint32_t lastUpdated = 0; 36 | uint32_t updatePeriod = 5000; 37 | 38 | uint32_t bLastUpdated = 0; 39 | uint32_t bUpdatePeriod = 25; 40 | 41 | void loop() { 42 | ArduinoOTA.handle(); 43 | //wait for 5s to boot - this is useful in case of a bootloop to keep OTA running 44 | if ( millis() < 10000 ) 45 | return; 46 | 47 | 48 | if ( millis() - lastUpdated > updatePeriod ) { 49 | if ( cc.getConnection() != WAIT_FOR_RESPONSE ){ 50 | cc.dumpStatus(); 51 | } 52 | int st; 53 | 54 | if ( cc.getConnection() == DISCONNECTED ){ 55 | Serial.print("Connecting..."); 56 | st = cc.connect(CHROMECASTIP); 57 | Serial.println(st); 58 | } else { 59 | //at this point, cc.volume and cc.isMuted should be valid 60 | connection_t c = cc.loop(); 61 | if ( c == WAIT_FOR_RESPONSE || c == CONNECT_TO_APPLICATION ){ 62 | updatePeriod = 50; 63 | } else if ( c == APPLICATION_RUNNING ){ 64 | updatePeriod = 500; 65 | //at this point, all public fields describing the casting 66 | //(e.g. cc.artist, cc.title) should be valid 67 | } else { 68 | updatePeriod = 5000; 69 | } 70 | } 71 | lastUpdated = millis(); 72 | } 73 | if ( millis() - bLastUpdated > bUpdatePeriod && cc.getConnection() == APPLICATION_RUNNING ){ 74 | bool prevSelect = bSelectPressed; 75 | bool prevLeft = bLeftPressed; 76 | bool prevRight = bRightPressed; 77 | bSelectPressed = digitalRead(B_SELECT) == LOW; 78 | bRightPressed = digitalRead(B_RIGHT) == LOW; 79 | bLeftPressed = digitalRead(B_LEFT) == LOW; 80 | 81 | if ( !bSelectPressed && prevSelect ){ //select released 82 | cc.pause(true); 83 | } 84 | 85 | if ( !bLeftPressed && prevLeft ){ //left released 86 | cc.prev(); 87 | } 88 | 89 | if ( !bRightPressed && prevRight ){ //right released 90 | cc.next(); 91 | } 92 | 93 | bLastUpdated = millis(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ArduCastControl", 3 | "keywords": "Chromecast, protocolbuffer, protobuf, CASTV2", 4 | "description": "Library to provide control on chromecast playback", 5 | "repository": 6 | { 7 | "type": "git", 8 | "url": "https://github.com/andrasbiro/ArduCastControl.git" 9 | }, 10 | "frameworks": "arduino", 11 | "platforms": ["espressif8266", "espressif32"], 12 | "dependencies" : 13 | [ 14 | {"name": "ArduinoJson"}, 15 | {"name": "Nanopb"} 16 | ], 17 | "version": "0.1.1" 18 | } -------------------------------------------------------------------------------- /logging.pb.c: -------------------------------------------------------------------------------- 1 | /* Automatically generated nanopb constant definitions */ 2 | /* Generated by nanopb-0.4.2 */ 3 | 4 | #include "logging.pb.h" 5 | #if PB_PROTO_HEADER_VERSION != 40 6 | #error Regenerate this file with the current version of nanopb generator. 7 | #endif 8 | 9 | PB_BIND(extensions_api_cast_channel_proto_SocketEvent, extensions_api_cast_channel_proto_SocketEvent, AUTO) 10 | 11 | 12 | PB_BIND(extensions_api_cast_channel_proto_AggregatedSocketEvent, extensions_api_cast_channel_proto_AggregatedSocketEvent, AUTO) 13 | 14 | 15 | PB_BIND(extensions_api_cast_channel_proto_Log, extensions_api_cast_channel_proto_Log, AUTO) 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /logging.pb.h: -------------------------------------------------------------------------------- 1 | /* Automatically generated nanopb header */ 2 | /* Generated by nanopb-0.4.2 */ 3 | 4 | #ifndef PB_EXTENSIONS_API_CAST_CHANNEL_PROTO_LOGGING_PB_H_INCLUDED 5 | #define PB_EXTENSIONS_API_CAST_CHANNEL_PROTO_LOGGING_PB_H_INCLUDED 6 | #include <pb.h> 7 | 8 | #if PB_PROTO_HEADER_VERSION != 40 9 | #error Regenerate this file with the current version of nanopb generator. 10 | #endif 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | /* Enum definitions */ 17 | typedef enum _extensions_api_cast_channel_proto_EventType { 18 | extensions_api_cast_channel_proto_EventType_EVENT_TYPE_UNKNOWN = 0, 19 | extensions_api_cast_channel_proto_EventType_CAST_SOCKET_CREATED = 1, 20 | extensions_api_cast_channel_proto_EventType_READY_STATE_CHANGED = 2, 21 | extensions_api_cast_channel_proto_EventType_CONNECTION_STATE_CHANGED = 3, 22 | extensions_api_cast_channel_proto_EventType_READ_STATE_CHANGED = 4, 23 | extensions_api_cast_channel_proto_EventType_WRITE_STATE_CHANGED = 5, 24 | extensions_api_cast_channel_proto_EventType_ERROR_STATE_CHANGED = 6, 25 | extensions_api_cast_channel_proto_EventType_CONNECT_FAILED = 7, 26 | extensions_api_cast_channel_proto_EventType_TCP_SOCKET_CONNECT = 8, 27 | extensions_api_cast_channel_proto_EventType_TCP_SOCKET_SET_KEEP_ALIVE = 9, 28 | extensions_api_cast_channel_proto_EventType_SSL_CERT_WHITELISTED = 10, 29 | extensions_api_cast_channel_proto_EventType_SSL_SOCKET_CONNECT = 11, 30 | extensions_api_cast_channel_proto_EventType_SSL_INFO_OBTAINED = 12, 31 | extensions_api_cast_channel_proto_EventType_DER_ENCODED_CERT_OBTAIN = 13, 32 | extensions_api_cast_channel_proto_EventType_RECEIVED_CHALLENGE_REPLY = 14, 33 | extensions_api_cast_channel_proto_EventType_AUTH_CHALLENGE_REPLY = 15, 34 | extensions_api_cast_channel_proto_EventType_CONNECT_TIMED_OUT = 16, 35 | extensions_api_cast_channel_proto_EventType_SEND_MESSAGE_FAILED = 17, 36 | extensions_api_cast_channel_proto_EventType_MESSAGE_ENQUEUED = 18, 37 | extensions_api_cast_channel_proto_EventType_SOCKET_WRITE = 19, 38 | extensions_api_cast_channel_proto_EventType_MESSAGE_WRITTEN = 20, 39 | extensions_api_cast_channel_proto_EventType_SOCKET_READ = 21, 40 | extensions_api_cast_channel_proto_EventType_MESSAGE_READ = 22, 41 | extensions_api_cast_channel_proto_EventType_SOCKET_CLOSED = 25, 42 | extensions_api_cast_channel_proto_EventType_SSL_CERT_EXCESSIVE_LIFETIME = 26, 43 | extensions_api_cast_channel_proto_EventType_CHANNEL_POLICY_ENFORCED = 27, 44 | extensions_api_cast_channel_proto_EventType_TCP_SOCKET_CONNECT_COMPLETE = 28, 45 | extensions_api_cast_channel_proto_EventType_SSL_SOCKET_CONNECT_COMPLETE = 29, 46 | extensions_api_cast_channel_proto_EventType_SSL_SOCKET_CONNECT_FAILED = 30, 47 | extensions_api_cast_channel_proto_EventType_SEND_AUTH_CHALLENGE_FAILED = 31, 48 | extensions_api_cast_channel_proto_EventType_AUTH_CHALLENGE_REPLY_INVALID = 32, 49 | extensions_api_cast_channel_proto_EventType_PING_WRITE_ERROR = 33 50 | } extensions_api_cast_channel_proto_EventType; 51 | 52 | typedef enum _extensions_api_cast_channel_proto_ChannelAuth { 53 | extensions_api_cast_channel_proto_ChannelAuth_SSL = 1, 54 | extensions_api_cast_channel_proto_ChannelAuth_SSL_VERIFIED = 2 55 | } extensions_api_cast_channel_proto_ChannelAuth; 56 | 57 | typedef enum _extensions_api_cast_channel_proto_ReadyState { 58 | extensions_api_cast_channel_proto_ReadyState_READY_STATE_NONE = 1, 59 | extensions_api_cast_channel_proto_ReadyState_READY_STATE_CONNECTING = 2, 60 | extensions_api_cast_channel_proto_ReadyState_READY_STATE_OPEN = 3, 61 | extensions_api_cast_channel_proto_ReadyState_READY_STATE_CLOSING = 4, 62 | extensions_api_cast_channel_proto_ReadyState_READY_STATE_CLOSED = 5 63 | } extensions_api_cast_channel_proto_ReadyState; 64 | 65 | typedef enum _extensions_api_cast_channel_proto_ConnectionState { 66 | extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_UNKNOWN = 1, 67 | extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_TCP_CONNECT = 2, 68 | extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_TCP_CONNECT_COMPLETE = 3, 69 | extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_SSL_CONNECT = 4, 70 | extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_SSL_CONNECT_COMPLETE = 5, 71 | extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_AUTH_CHALLENGE_SEND = 6, 72 | extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_AUTH_CHALLENGE_SEND_COMPLETE = 7, 73 | extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_AUTH_CHALLENGE_REPLY_COMPLETE = 8, 74 | extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_START_CONNECT = 9, 75 | extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_FINISHED = 100, 76 | extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_ERROR = 101, 77 | extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_TIMEOUT = 102 78 | } extensions_api_cast_channel_proto_ConnectionState; 79 | 80 | typedef enum _extensions_api_cast_channel_proto_ReadState { 81 | extensions_api_cast_channel_proto_ReadState_READ_STATE_UNKNOWN = 1, 82 | extensions_api_cast_channel_proto_ReadState_READ_STATE_READ = 2, 83 | extensions_api_cast_channel_proto_ReadState_READ_STATE_READ_COMPLETE = 3, 84 | extensions_api_cast_channel_proto_ReadState_READ_STATE_DO_CALLBACK = 4, 85 | extensions_api_cast_channel_proto_ReadState_READ_STATE_HANDLE_ERROR = 5, 86 | extensions_api_cast_channel_proto_ReadState_READ_STATE_ERROR = 100 87 | } extensions_api_cast_channel_proto_ReadState; 88 | 89 | typedef enum _extensions_api_cast_channel_proto_WriteState { 90 | extensions_api_cast_channel_proto_WriteState_WRITE_STATE_UNKNOWN = 1, 91 | extensions_api_cast_channel_proto_WriteState_WRITE_STATE_WRITE = 2, 92 | extensions_api_cast_channel_proto_WriteState_WRITE_STATE_WRITE_COMPLETE = 3, 93 | extensions_api_cast_channel_proto_WriteState_WRITE_STATE_DO_CALLBACK = 4, 94 | extensions_api_cast_channel_proto_WriteState_WRITE_STATE_HANDLE_ERROR = 5, 95 | extensions_api_cast_channel_proto_WriteState_WRITE_STATE_ERROR = 100, 96 | extensions_api_cast_channel_proto_WriteState_WRITE_STATE_IDLE = 101 97 | } extensions_api_cast_channel_proto_WriteState; 98 | 99 | typedef enum _extensions_api_cast_channel_proto_ErrorState { 100 | extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_NONE = 1, 101 | extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_CHANNEL_NOT_OPEN = 2, 102 | extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_AUTHENTICATION_ERROR = 3, 103 | extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_CONNECT_ERROR = 4, 104 | extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_SOCKET_ERROR = 5, 105 | extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_TRANSPORT_ERROR = 6, 106 | extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_INVALID_MESSAGE = 7, 107 | extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_INVALID_CHANNEL_ID = 8, 108 | extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_CONNECT_TIMEOUT = 9, 109 | extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_UNKNOWN = 10 110 | } extensions_api_cast_channel_proto_ErrorState; 111 | 112 | typedef enum _extensions_api_cast_channel_proto_ChallengeReplyErrorType { 113 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_NONE = 1, 114 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_PEER_CERT_EMPTY = 2, 115 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_WRONG_PAYLOAD_TYPE = 3, 116 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_NO_PAYLOAD = 4, 117 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_PAYLOAD_PARSING_FAILED = 5, 118 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_MESSAGE_ERROR = 6, 119 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_NO_RESPONSE = 7, 120 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_FINGERPRINT_NOT_FOUND = 8, 121 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_CERT_PARSING_FAILED = 9, 122 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA = 10, 123 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_CANNOT_EXTRACT_PUBLIC_KEY = 11, 124 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_SIGNED_BLOBS_MISMATCH = 12, 125 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_TLS_CERT_VALIDITY_PERIOD_TOO_LONG = 13, 126 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_TLS_CERT_VALID_START_DATE_IN_FUTURE = 14, 127 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_TLS_CERT_EXPIRED = 15, 128 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_CRL_INVALID = 16, 129 | extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_CERT_REVOKED = 17 130 | } extensions_api_cast_channel_proto_ChallengeReplyErrorType; 131 | 132 | /* Struct definitions */ 133 | typedef struct _extensions_api_cast_channel_proto_AggregatedSocketEvent { 134 | bool has_id; 135 | int32_t id; 136 | bool has_endpoint_id; 137 | int32_t endpoint_id; 138 | bool has_channel_auth_type; 139 | extensions_api_cast_channel_proto_ChannelAuth channel_auth_type; 140 | pb_callback_t socket_event; 141 | bool has_bytes_read; 142 | int64_t bytes_read; 143 | bool has_bytes_written; 144 | int64_t bytes_written; 145 | } extensions_api_cast_channel_proto_AggregatedSocketEvent; 146 | 147 | typedef struct _extensions_api_cast_channel_proto_Log { 148 | pb_callback_t aggregated_socket_event; 149 | bool has_num_evicted_aggregated_socket_events; 150 | int32_t num_evicted_aggregated_socket_events; 151 | bool has_num_evicted_socket_events; 152 | int32_t num_evicted_socket_events; 153 | } extensions_api_cast_channel_proto_Log; 154 | 155 | typedef struct _extensions_api_cast_channel_proto_SocketEvent { 156 | bool has_type; 157 | extensions_api_cast_channel_proto_EventType type; 158 | bool has_timestamp_micros; 159 | int64_t timestamp_micros; 160 | pb_callback_t details; 161 | bool has_net_return_value; 162 | int32_t net_return_value; 163 | pb_callback_t message_namespace; 164 | bool has_ready_state; 165 | extensions_api_cast_channel_proto_ReadyState ready_state; 166 | bool has_connection_state; 167 | extensions_api_cast_channel_proto_ConnectionState connection_state; 168 | bool has_read_state; 169 | extensions_api_cast_channel_proto_ReadState read_state; 170 | bool has_write_state; 171 | extensions_api_cast_channel_proto_WriteState write_state; 172 | bool has_error_state; 173 | extensions_api_cast_channel_proto_ErrorState error_state; 174 | bool has_challenge_reply_error_type; 175 | extensions_api_cast_channel_proto_ChallengeReplyErrorType challenge_reply_error_type; 176 | bool has_nss_error_code; 177 | int32_t nss_error_code; 178 | } extensions_api_cast_channel_proto_SocketEvent; 179 | 180 | 181 | /* Helper constants for enums */ 182 | #define _extensions_api_cast_channel_proto_EventType_MIN extensions_api_cast_channel_proto_EventType_EVENT_TYPE_UNKNOWN 183 | #define _extensions_api_cast_channel_proto_EventType_MAX extensions_api_cast_channel_proto_EventType_PING_WRITE_ERROR 184 | #define _extensions_api_cast_channel_proto_EventType_ARRAYSIZE ((extensions_api_cast_channel_proto_EventType)(extensions_api_cast_channel_proto_EventType_PING_WRITE_ERROR+1)) 185 | 186 | #define _extensions_api_cast_channel_proto_ChannelAuth_MIN extensions_api_cast_channel_proto_ChannelAuth_SSL 187 | #define _extensions_api_cast_channel_proto_ChannelAuth_MAX extensions_api_cast_channel_proto_ChannelAuth_SSL_VERIFIED 188 | #define _extensions_api_cast_channel_proto_ChannelAuth_ARRAYSIZE ((extensions_api_cast_channel_proto_ChannelAuth)(extensions_api_cast_channel_proto_ChannelAuth_SSL_VERIFIED+1)) 189 | 190 | #define _extensions_api_cast_channel_proto_ReadyState_MIN extensions_api_cast_channel_proto_ReadyState_READY_STATE_NONE 191 | #define _extensions_api_cast_channel_proto_ReadyState_MAX extensions_api_cast_channel_proto_ReadyState_READY_STATE_CLOSED 192 | #define _extensions_api_cast_channel_proto_ReadyState_ARRAYSIZE ((extensions_api_cast_channel_proto_ReadyState)(extensions_api_cast_channel_proto_ReadyState_READY_STATE_CLOSED+1)) 193 | 194 | #define _extensions_api_cast_channel_proto_ConnectionState_MIN extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_UNKNOWN 195 | #define _extensions_api_cast_channel_proto_ConnectionState_MAX extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_TIMEOUT 196 | #define _extensions_api_cast_channel_proto_ConnectionState_ARRAYSIZE ((extensions_api_cast_channel_proto_ConnectionState)(extensions_api_cast_channel_proto_ConnectionState_CONN_STATE_TIMEOUT+1)) 197 | 198 | #define _extensions_api_cast_channel_proto_ReadState_MIN extensions_api_cast_channel_proto_ReadState_READ_STATE_UNKNOWN 199 | #define _extensions_api_cast_channel_proto_ReadState_MAX extensions_api_cast_channel_proto_ReadState_READ_STATE_ERROR 200 | #define _extensions_api_cast_channel_proto_ReadState_ARRAYSIZE ((extensions_api_cast_channel_proto_ReadState)(extensions_api_cast_channel_proto_ReadState_READ_STATE_ERROR+1)) 201 | 202 | #define _extensions_api_cast_channel_proto_WriteState_MIN extensions_api_cast_channel_proto_WriteState_WRITE_STATE_UNKNOWN 203 | #define _extensions_api_cast_channel_proto_WriteState_MAX extensions_api_cast_channel_proto_WriteState_WRITE_STATE_IDLE 204 | #define _extensions_api_cast_channel_proto_WriteState_ARRAYSIZE ((extensions_api_cast_channel_proto_WriteState)(extensions_api_cast_channel_proto_WriteState_WRITE_STATE_IDLE+1)) 205 | 206 | #define _extensions_api_cast_channel_proto_ErrorState_MIN extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_NONE 207 | #define _extensions_api_cast_channel_proto_ErrorState_MAX extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_UNKNOWN 208 | #define _extensions_api_cast_channel_proto_ErrorState_ARRAYSIZE ((extensions_api_cast_channel_proto_ErrorState)(extensions_api_cast_channel_proto_ErrorState_CHANNEL_ERROR_UNKNOWN+1)) 209 | 210 | #define _extensions_api_cast_channel_proto_ChallengeReplyErrorType_MIN extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_NONE 211 | #define _extensions_api_cast_channel_proto_ChallengeReplyErrorType_MAX extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_CERT_REVOKED 212 | #define _extensions_api_cast_channel_proto_ChallengeReplyErrorType_ARRAYSIZE ((extensions_api_cast_channel_proto_ChallengeReplyErrorType)(extensions_api_cast_channel_proto_ChallengeReplyErrorType_CHALLENGE_REPLY_ERROR_CERT_REVOKED+1)) 213 | 214 | 215 | /* Initializer values for message structs */ 216 | #define extensions_api_cast_channel_proto_SocketEvent_init_default {false, _extensions_api_cast_channel_proto_EventType_MIN, false, 0, {{NULL}, NULL}, false, 0, {{NULL}, NULL}, false, _extensions_api_cast_channel_proto_ReadyState_MIN, false, _extensions_api_cast_channel_proto_ConnectionState_MIN, false, _extensions_api_cast_channel_proto_ReadState_MIN, false, _extensions_api_cast_channel_proto_WriteState_MIN, false, _extensions_api_cast_channel_proto_ErrorState_MIN, false, _extensions_api_cast_channel_proto_ChallengeReplyErrorType_MIN, false, 0} 217 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_init_default {false, 0, false, 0, false, _extensions_api_cast_channel_proto_ChannelAuth_MIN, {{NULL}, NULL}, false, 0, false, 0} 218 | #define extensions_api_cast_channel_proto_Log_init_default {{{NULL}, NULL}, false, 0, false, 0} 219 | #define extensions_api_cast_channel_proto_SocketEvent_init_zero {false, _extensions_api_cast_channel_proto_EventType_MIN, false, 0, {{NULL}, NULL}, false, 0, {{NULL}, NULL}, false, _extensions_api_cast_channel_proto_ReadyState_MIN, false, _extensions_api_cast_channel_proto_ConnectionState_MIN, false, _extensions_api_cast_channel_proto_ReadState_MIN, false, _extensions_api_cast_channel_proto_WriteState_MIN, false, _extensions_api_cast_channel_proto_ErrorState_MIN, false, _extensions_api_cast_channel_proto_ChallengeReplyErrorType_MIN, false, 0} 220 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_init_zero {false, 0, false, 0, false, _extensions_api_cast_channel_proto_ChannelAuth_MIN, {{NULL}, NULL}, false, 0, false, 0} 221 | #define extensions_api_cast_channel_proto_Log_init_zero {{{NULL}, NULL}, false, 0, false, 0} 222 | 223 | /* Field tags (for use in manual encoding/decoding) */ 224 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_id_tag 1 225 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_endpoint_id_tag 2 226 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_channel_auth_type_tag 3 227 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_socket_event_tag 4 228 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_bytes_read_tag 5 229 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_bytes_written_tag 6 230 | #define extensions_api_cast_channel_proto_Log_aggregated_socket_event_tag 1 231 | #define extensions_api_cast_channel_proto_Log_num_evicted_aggregated_socket_events_tag 2 232 | #define extensions_api_cast_channel_proto_Log_num_evicted_socket_events_tag 3 233 | #define extensions_api_cast_channel_proto_SocketEvent_type_tag 1 234 | #define extensions_api_cast_channel_proto_SocketEvent_timestamp_micros_tag 2 235 | #define extensions_api_cast_channel_proto_SocketEvent_details_tag 3 236 | #define extensions_api_cast_channel_proto_SocketEvent_net_return_value_tag 4 237 | #define extensions_api_cast_channel_proto_SocketEvent_message_namespace_tag 5 238 | #define extensions_api_cast_channel_proto_SocketEvent_ready_state_tag 6 239 | #define extensions_api_cast_channel_proto_SocketEvent_connection_state_tag 7 240 | #define extensions_api_cast_channel_proto_SocketEvent_read_state_tag 8 241 | #define extensions_api_cast_channel_proto_SocketEvent_write_state_tag 9 242 | #define extensions_api_cast_channel_proto_SocketEvent_error_state_tag 10 243 | #define extensions_api_cast_channel_proto_SocketEvent_challenge_reply_error_type_tag 11 244 | #define extensions_api_cast_channel_proto_SocketEvent_nss_error_code_tag 12 245 | 246 | /* Struct field encoding specification for nanopb */ 247 | #define extensions_api_cast_channel_proto_SocketEvent_FIELDLIST(X, a) \ 248 | X(a, STATIC, OPTIONAL, UENUM, type, 1) \ 249 | X(a, STATIC, OPTIONAL, INT64, timestamp_micros, 2) \ 250 | X(a, CALLBACK, OPTIONAL, STRING, details, 3) \ 251 | X(a, STATIC, OPTIONAL, INT32, net_return_value, 4) \ 252 | X(a, CALLBACK, OPTIONAL, STRING, message_namespace, 5) \ 253 | X(a, STATIC, OPTIONAL, UENUM, ready_state, 6) \ 254 | X(a, STATIC, OPTIONAL, UENUM, connection_state, 7) \ 255 | X(a, STATIC, OPTIONAL, UENUM, read_state, 8) \ 256 | X(a, STATIC, OPTIONAL, UENUM, write_state, 9) \ 257 | X(a, STATIC, OPTIONAL, UENUM, error_state, 10) \ 258 | X(a, STATIC, OPTIONAL, UENUM, challenge_reply_error_type, 11) \ 259 | X(a, STATIC, OPTIONAL, INT32, nss_error_code, 12) 260 | #define extensions_api_cast_channel_proto_SocketEvent_CALLBACK pb_default_field_callback 261 | #define extensions_api_cast_channel_proto_SocketEvent_DEFAULT (const pb_byte_t*)"\x30\x01\x38\x01\x40\x01\x48\x01\x50\x01\x58\x01\x00" 262 | 263 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_FIELDLIST(X, a) \ 264 | X(a, STATIC, OPTIONAL, INT32, id, 1) \ 265 | X(a, STATIC, OPTIONAL, INT32, endpoint_id, 2) \ 266 | X(a, STATIC, OPTIONAL, UENUM, channel_auth_type, 3) \ 267 | X(a, CALLBACK, REPEATED, MESSAGE, socket_event, 4) \ 268 | X(a, STATIC, OPTIONAL, INT64, bytes_read, 5) \ 269 | X(a, STATIC, OPTIONAL, INT64, bytes_written, 6) 270 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_CALLBACK pb_default_field_callback 271 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_DEFAULT (const pb_byte_t*)"\x18\x01\x00" 272 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_socket_event_MSGTYPE extensions_api_cast_channel_proto_SocketEvent 273 | 274 | #define extensions_api_cast_channel_proto_Log_FIELDLIST(X, a) \ 275 | X(a, CALLBACK, REPEATED, MESSAGE, aggregated_socket_event, 1) \ 276 | X(a, STATIC, OPTIONAL, INT32, num_evicted_aggregated_socket_events, 2) \ 277 | X(a, STATIC, OPTIONAL, INT32, num_evicted_socket_events, 3) 278 | #define extensions_api_cast_channel_proto_Log_CALLBACK pb_default_field_callback 279 | #define extensions_api_cast_channel_proto_Log_DEFAULT NULL 280 | #define extensions_api_cast_channel_proto_Log_aggregated_socket_event_MSGTYPE extensions_api_cast_channel_proto_AggregatedSocketEvent 281 | 282 | extern const pb_msgdesc_t extensions_api_cast_channel_proto_SocketEvent_msg; 283 | extern const pb_msgdesc_t extensions_api_cast_channel_proto_AggregatedSocketEvent_msg; 284 | extern const pb_msgdesc_t extensions_api_cast_channel_proto_Log_msg; 285 | 286 | /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ 287 | #define extensions_api_cast_channel_proto_SocketEvent_fields &extensions_api_cast_channel_proto_SocketEvent_msg 288 | #define extensions_api_cast_channel_proto_AggregatedSocketEvent_fields &extensions_api_cast_channel_proto_AggregatedSocketEvent_msg 289 | #define extensions_api_cast_channel_proto_Log_fields &extensions_api_cast_channel_proto_Log_msg 290 | 291 | /* Maximum encoded size of messages (where known) */ 292 | /* extensions_api_cast_channel_proto_SocketEvent_size depends on runtime parameters */ 293 | /* extensions_api_cast_channel_proto_AggregatedSocketEvent_size depends on runtime parameters */ 294 | /* extensions_api_cast_channel_proto_Log_size depends on runtime parameters */ 295 | 296 | #ifdef __cplusplus 297 | } /* extern "C" */ 298 | #endif 299 | 300 | #endif 301 | --------------------------------------------------------------------------------