Intermediate - Receiving and Using Values (with SPIKE Service)

The Local Page

Continuing with the slider-controlled motor example, we now have the remote page down. But what about the local one? The two main steps we need to accomplish here are 1) getting the speed out of Airtable, and 2) sending that speed to the SPIKE Prime. Let's start with step 1.

Listening for Change

Assuming we have everything set up correctly, with creation and initialization of a Service_Airtable object identical to that of the remote page, getting a value out of Airtable is trivial; all we have to do is call getEntryValue(name) on the Service_Airtable object and use the value returned however we please. But how do we know when to check?

As developers, we know that the speed in the table is updated whenever the slider on the remote page is moved. But how does the local page know that? And even if it did, how would it be at all aware of when some user halfway across the world happened to click that slider? On the local end, we don't know when a change will occur, so we have to write a function to regularly check for one.

A common first instinct for this problem, especially for someone accustomed to writing MicroPython SPIKE Prime programs, might be to just put the getEntryValue call in a while loop and call it a day. Unfortunately, this is likely to run too often and cause the webpage to crash with the overload of commands. Instead, we want to set up our checks to happen at regular intervals, which can be accomplished with JavaScript timing functions.

In this tutorial, we're going to use setTimeout(function, milliseconds), which schedules a given function to run a given number of milliseconds after the setTimeout line runs. We could also just as easily use setInterval(function, milliseconds), which schedules periodic calls of a given function, as explained here; my use of getTimeout instead is mostly an aesthetic choice.

Knowing that, the code becomes fairly simple; we get the current speed from Airtable, and, if it is different than the speed from the last check, update our speed display on the page (and/or the speed of a connected SPIKE motor, but we aren't tackling that part yet). Then, at the end of that checking function, we schedule the next check for one second later, and so on into infinity (or until we close the page).

In code, that process would look something like this:

serviceAirtable.executeAfterInit(function() {
    // start the periodic checks and updates
    checkAndUpdate(null)
})

// checks airtable for new motor speed, updates page display accordingly, and sets up next check if program is still active
function checkAndUpdate(pastSpeed) {
    var currentSpeed = serviceAirtable.getEntryValue("motor_speed")
    // if speed has been changed since last check, update page display
    if(currentSpeed != pastSpeed)
    updateSpeed(currentSpeed)

    // check again in one second
    setTimeout(function() { checkAndUpdate(currentSpeed) }, 1000)
}

// changes speed displayed on page to given newSpeed
function updateSpeed(newSpeed) {
    document.getElementById("speed_display").innerText = "Speed: " + newSpeed;
}

An Edge Case

Suppose a user connects a local page to the Airtable before a remote page is ever connected. Since a value for motor_speed has never been set, the getEntryValue will throw an error, which will halt our regular checking of the table such that even if a remote page is later connected, the local page will not receive any values. Before starting those periodic checks, we therefore have to make sure the motor_speed entry exists in order to prevent the page from trying to get a nonexistent value.

The Service_Airtable function getEntriesInfo() returns an object with a field representing each entry in the table. You can access one such entry by putting its name in brackets, such that, if motor-speed exists in the table, the line serviceAirtable.getEntriesInfo()["motor-speed"] will return an object containing the name "motor-speed" and its current value. If the motor-speed entry is not in the table, it will return undefined.

Knowing that, we can write a function very similar to checkAndUpdate that checks if motor-speed exists and, if so, starts the periodic calls of checkAndUpdate. If not, it can schedule itself to check again in one second, and continue doing so until a motor-speed entry is found. In code, that would look like this:

function startChecking() {
    if(serviceAirtable.getEntriesInfo()["motor_speed"] != undefined)
        checkAndUpdate(null)
    else
        setTimeout(startChecking, 1000)
}

Adding In the SPIKE

Now that we have the speed, all we have to do is send it to a motor! Assuming some familiarity with JavaScript-based SPIKE Prime control, adding this in should be fairly trivial (if not, feel free to check out this tutorial, where we connect to and start a SPIKE motor). After adding in and initialize a Service_SPIKE, we just have to add a few lines to updateSpeed to send the new speed to the attached SPIKE motor. The final code will look something like this:

<html>
    <head>
        <script src="https://cdn.jsdelivr.net/gh/tuftsceeo/SPIKE-Web-Interface@1.0/cdn/ServiceDock.min.js"></script>
        <style>
            #speed_display {
                text-align: center;
                font-size: 30px;
                padding: 100px;
            }
        </style>
    </head>
    <body>
         <div id = "servicedock" style = "float:right;">
            <service-airtable id = "service_airtable"></service-airtable>
            <service-spike id="service_spike"></service-spike>
        </div>
        <div id = "speed_display">Speed: unknown</div>
    </body>
    <script>
        var elementServiceAirtable = document.getElementById("service_airtable")
        elementServiceAirtable.setAttribute("apikey", "your_API_key")
        elementServiceAirtable.setAttribute("baseid", "your_base_ID")
        elementServiceAirtable.setAttribute("tablename", "your_table_name")
        elementServiceAirtable.init()

        var serviceAirtable = elementServiceAirtable.getService()
        var serviceSPIKE = document.getElementById("service_spike").getService()


        serviceAirtable.executeAfterInit(function() {
            // start the periodic checks and updates
            startChecking()
        })

        // starts periodic updates of and checks of motor speed if value exists in table (if not, tries again in 1 second)
        function startChecking() {
            if(serviceAirtable.getEntriesInfo()["motor_speed"] != undefined)
                checkAndUpdate(null)
            else
                setTimeout(startChecking, 1000)
        }

        // checks airtable for new motor speed, updates page display and SPIKE motor accordingly, and sets up next check if program is still active
        function checkAndUpdate(pastSpeed) {
            var currentSpeed = serviceAirtable.getEntryValue("motor_speed")
            // if speed has been changed since last check, update page display
            if(currentSpeed != pastSpeed)
                updateSpeed(currentSpeed)

            // check again in one second
            setTimeout(function() { checkAndUpdate(currentSpeed) }, 1000)
        }

        // changes speed displayed on page and speed of SPIKE motor to given newSpeed
        function updateSpeed(newSpeed) {
            document.getElementById("speed_display").innerText = "Speed: " + newSpeed
            
            // run motor if SPIKE is connected
            if(serviceSPIKE.isActive())
                serviceSPIKE.Motor("A").start(newSpeed)
        }
    </script>
</html>

And we're done! Both the remote and local page are on display below; feel free to plug in a SPIKE and test them out (note that the Airtable service is uninitialized in these displays, so you're going to have to click the airtable icon and input your own API Key, Base ID, and Table Name on each page for it to work).

Remote:

Local: