Saturday, November 15, 2014

Controlling a powerpoint presentation from the PEBBLE watch

The winRemoteControl api is HTTP based and therefore any http client can talk to the winRemoteControl Server to remote control a Windows machine.

I developed a client for the PEBBLE Watch, that simply send the keystrokes:

  • RIGHT
  • LEFT
  • ESCAPE
 With this I can control a PowerPoint presentation from the Watch.



To create the PEBBLE app, I used the WinFormebble C library, that make developing for the Watch more friendly to C# and WinForm developer.

The all source code is part of the library WinFormebble on github.

Here is the C part of the app
   
/*
 * A Pebble Watchface that control a PowerPoint presentation
 * using the winRemoteControl api
 * Demo for Code Camp 22, MA, USA
 * (C) Torres Frederic 2014
 * Licence: MIT
 */
#include <pebble.h>  
#include <pebble_fonts.h>
#include "WinFormebble.h"

// Api data returned from JavaScript World to C world
// Dictionary properties used in communication with Pebble iOS App
// Metadata defined in project settings    
#define KEY_REQUEST_ID 0
#define KEY_REQUEST_ID_NEXT 0
#define KEY_REQUEST_ID_PREVIOUS 1
#define KEY_REQUEST_ID_QUIT 2
#define KEY_REQUEST_ID_OK = 3;
    
// Data Refresh Rate
#define DEFAULT_STRING_BUFFER_SIZE 16    

// Strings and messages
#define WATCH_DIGIT_BUFFER "00:00"
    
#define WATCH_MAX_WIDTH 144    
    
Form mainForm;

    Label lblTime;
    Label lblAction;
    Label lblPage;

    int _pageCounter = 1;

    private void butSelect_Click(ClickRecognizerRef recognizer, void *context) {
        
        Label_SetText(lblAction, "Quit");
        jsCom_SendIntMessage(KEY_REQUEST_ID, KEY_REQUEST_ID_QUIT);
    }
    private void butUp_Click(ClickRecognizerRef recognizer, void *context) {
        
        Label_SetText(lblAction, "Previous");
        jsCom_SendIntMessage(KEY_REQUEST_ID, KEY_REQUEST_ID_PREVIOUS);
        _pageCounter--;
        if(_pageCounter == 0)
            _pageCounter = 1;
    }
    private void butDown_Click(ClickRecognizerRef recognizer, void *context) {
        
        Label_SetText(lblAction, "Next");
        jsCom_SendIntMessage(KEY_REQUEST_ID, KEY_REQUEST_ID_NEXT);
        _pageCounter++;
    }
    private void mainForm_Load(Window *window) {
        
        int y = 15;
        lblTime = Label_New(GRect(0, y, WATCH_MAX_WIDTH, 50), WhiteBackground, GTextAlignmentCenter, FONT_KEY_BITHAM_30_BLACK);
        Label_SetText(lblTime, WATCH_DIGIT_BUFFER);
        Form_AddLabel(mainForm, lblTime);
        y += 45;
        
        lblAction = Label_New(GRect(0, y, WATCH_MAX_WIDTH, 50), WhiteBackground, GTextAlignmentCenter, FONT_KEY_BITHAM_30_BLACK);
        Label_SetText(lblAction, "None");
        Form_AddLabel(mainForm, lblAction);
        y += 45;
        
        lblPage = Label_New(GRect(0, y, WATCH_MAX_WIDTH, 50), WhiteBackground, GTextAlignmentCenter, FONT_KEY_BITHAM_30_BLACK);
        Label_SetText(lblPage, "Page: 1");
        Form_AddLabel(mainForm, lblPage);
        
        Form_RegisterButtonHandlers(mainForm, butSelect_Click, butUp_Click, butDown_Click);
    }
    private void mainForm_Unload(Window *window) {
        
    }  
    private void mainForm_UpdateTime() {    
        
        struct tm *tick_time   = DateTime_Now();
        static char timeBuffer [DEFAULT_STRING_BUFFER_SIZE];
        Label_SetText(lblTime,  StringFormatTime(tick_time, "%T", timeBuffer));
    }
    private void mainForm_Timer(struct tm *tick_time, TimeUnits units_changed) {
        
        mainForm_UpdateTime();                 
    }
    private void  mainForm_InboxReceivedCallback(DictionaryIterator *iterator, void *context) {
        static char pageCounterBuffer [DEFAULT_STRING_BUFFER_SIZE];
        Tuple * t = dict_read_first(iterator);
        while(t != NULL) {
            
            switch(t->key) {
                case KEY_REQUEST_ID : 
                    Label_SetText(lblPage, StringFormatInt(_pageCounter, "Page: %d", pageCounterBuffer));
                break;
                default             : APP_LOG(APP_LOG_LEVEL_ERROR, "Key %d not recognized!", (int)t->key); break;
            }
            t = dict_read_next(iterator);
        }
    }

int main(void) { 
    
    mainForm = Form_New();
    Form_Initialize(mainForm, mainForm_Load, mainForm_Unload);
    Form_Show(mainForm);
    mainForm_UpdateTime();
    jsCom_Initialize(mainForm_InboxReceivedCallback);
    Form_RegisterWatchFaceTimer(SECOND_UNIT, mainForm_Timer);    
        
    app_event_loop();
    
    Form_Destructor(mainForm);  // Also clean all associated controls
}

Here is the JavaScript part of the app
   
/*
 * A Pebble Watchface that control a PowerPoint presentation
 * using the winRemoteControl api
 * Demo for Code Camp 22, MA, USA
 * (C) Torres Frederic 2014
 * Licence: MIT
 */

var COMPUTER_IP             = "137.116.241.239";
var KEY_REQUEST_ID_NEXT     = 0;
var KEY_REQUEST_ID_PREVIOUS = 1;
var KEY_REQUEST_ID_QUIT     = 2; 
var KEY_REQUEST_ID_OK       = 3; 

var URL            = 'http://[IP]:1964/WinRemoteControl?json=';
var URL_JSON_PARAM = '{"Action":2,"Keys":"{[KEY]}"}';

function sendCommand(key) {
    
    var param, keyValue;
    switch(key) {
        case KEY_REQUEST_ID_NEXT     : keyValue = "RIGHT";  break;
        case KEY_REQUEST_ID_PREVIOUS : keyValue = "LEFT";   break;
        case KEY_REQUEST_ID_QUIT     : keyValue = "ESCAPE"; break;
        default: return;
    }
    param     = URL_JSON_PARAM.replace("[KEY]", keyValue);
    var url   = URL.replace('[IP]', COMPUTER_IP);
    Trace(url + param);
    url      += encodeURIComponent(param);
    
    httpGet(url,
        function(responseText) {            
            var dictionary  = { KEY_REQUEST_ID : KEY_REQUEST_ID_OK };
            sendMessageToWatch(dictionary, "sendCommand");
        }      
    );
}

function Init() {
    
    Pebble.addEventListener('ready',  // Listen for when the watchface is opened
        function(e) {
            Trace("App started, connected:{0}, Browser Agent:{1}".format(e.ready, navigator.userAgent));
        }
    );    
    Pebble.addEventListener('appmessage', // Listen for when an AppMessage is received
        function(e) {
            Trace("Requested From App:"+JSON.stringify(e.payload));
            sendCommand(e.payload.KEY_REQUEST_ID);
        }
    );
}

Init();

1 comment:

  1. Thanks for the helpful guide on controlling PowerPoint presentations remotely! I found the steps very clear and easy to follow. It’s great to see such a practical solution for managing presentations from a distance.

    I have a quick question. What if I’m using a non-standard remote control or a different type of device for the remote control? Are there any specific settings or additional software needed to make it compatible with PowerPoint? Also, do you have any tips for troubleshooting if the remote isn’t responding during a presentation?

    Check out this Pay by Paypal for more information.

    ReplyDelete