So I propose that we acknowledge these as guidance systems, and provide some mentorship and coaching regarding the risks in using them, and ways to mitigate that risk. In particular, I see an opportunity to motivate and to teach software testing as even more critical as we rely more heavily on automatically generated code (or, for that matter, code that one finds from anonymous sources on the Internet!).
To try this out, I provided the following prompt to ChatGPT:
Write a python program that extracts N bits from an IPv4 address given a netmask N
And I got the following response:
def extract_bits(ip_address, netmask_bits):
# Convert IP address to a list of integers
ip_list = [int(x) for x in ip_address.split(".")]
# Create a binary string of the netmask
netmask = "1" * netmask_bits + "0" * (32 - netmask_bits)
# Convert the binary netmask to a list of integers
netmask_list = [int(netmask[i:i+8], 2) for i in range(0, 32, 8)]
# Apply the netmask to the IP address
masked_ip = [ip_list[i] & netmask_list[i] for i in range(4)]
# Convert the masked IP address to a binary string
masked_ip_str = "".join(format(byte, "08b") for byte in masked_ip)
# Extract the first N bits from the masked IP address
extracted_bits = masked_ip_str[:netmask_bits]
# Convert the extracted bits to an integer
extracted_int = int(extracted_bits, 2)
return extracted_int
It even provided me with an example on how to invoke this function:
# In this example, the function extracts the first 24 bits (corresponding to the first three octets) from the IP address "192.168.1.100" using a netmask of 24 bits. The resulting value is 192.
ip_address = "192.168.1.100"
netmask_bits = 24
extracted_bits = extract_bits(ip_address, netmask_bits)
print(extracted_bits) # Output: 192
Pretty impressive, even if it is simply mining the Internet for text to synthesize as a response. The problem is that it is not clear that this program is generating the correct output. When I call this function according to the example it gave, I get 12625921
as the output. Incidentally, that’s 0b0b110000001010100000000001
in binary. That does correspond to the first 24 bits of the address. But the comment says to expect the output to be 192, which is not quite right. Even still, it would be nice to report the network address in the typical a.b.c.d
format, or at least as a list of octets, such as [192, 168, 1, 0]
. Perhaps this was the result of ambiguity in the question I posed to ChatGPT, but regardless, I’m curious as to whether we could continue to use ChatGPT prompts to finish this program.
I decided to ask ChatGPT to write another program to help me to do this, by converting a 32-bit integer (that the original function was returning to me) into a list of 4 octets, like a typical IPv4 network address:
Write a program to convert an integer containing 32 bits to a list of four 8-bit values in python
To which it wrote:
def int_to_bytes(n):
# Convert integer to binary string
binary_str = format(n, "032b")
# Split binary string into four 8-bit chunks
byte_strs = [binary_str[i:i+8] for i in range(0, 32, 8)]
# Convert each 8-bit chunk to an integer
bytes = [int(byte_str, 2) for byte_str in byte_strs]
return bytes
It provided the following example invocation:
# In this example, the function converts the integer 4278190080 to a binary string ("11111111000000000000000011111111") and then splits it into four 8-bit chunks ("11111111", "00000000", "00000000", and "11111111"). Finally, it converts each 8-bit chunk to an integer ([255, 0, 0, 255]). The resulting value is a list of four 8-bit values that represent the number 4278190080 in binary.
n = 4278190080
bytes = int_to_bytes(n)
print(bytes) # Output: [255, 0, 0, 255]
Interesting, it got the comment wrong in the example again. [255, 0, 0, 0]
is the expected output for this example.
To combine these two programs, I called int_to_bytes
from the original extract_bits
function, right before the return statement. To do this, I had to shift the network address to the left, since the network address bits are the uppermost bits of an IPv4 address. Then, I called int_to_bytes
with this resulting value. Although I did this manually, I think it would be an interesting assignment to ask students to continue to prompt ChatGPT until it came up with this solution, sort of like a coding version of the game “Taboo.” Here’s the solution, now ask the right question to get an AI to generate it. It’s a little more challenging that it sounds. For example, one wouldn’t know to ask the AI to shift the bits of the network address without an understanding of IP address formats.
# PAD 0's ON THE LEAST SIGNIFICANT (COMPUTER ADDRESS) BITS
extracted_int = extracted_int << (32 - netmask_bits)
# CONVERT BACK TO OCTETS
extracted_int = int_to_bytes(extracted_int)
Additionally, we should take care to validate this program. We always say this is a best practice, but students are usually pretty confident in their solutions. Perhaps they’d be more convinced that some arbitrary code shouldn’t be trusted, especially if the output does not make sense, and if it does not align with the generated comment. Another fun exercise is to generate unit tests for AI generated code, and to revise the prompts until the tests pass. Using pytest
, here are a few brief tests for the code that my prompts generated here:
# pip install pytest
# run with: pytest
from to_octets import int_to_bytes
from netmask2 import extract_bits
def test_netmask():
assert extract_bits("192.168.1.100", 24) == [192, 168, 1, 0]
def test_int_to_bytes():
assert int_to_bytes(4278190080) == [255, 0, 0, 0]
Impressively, ChatGPT got this program nearly correct on the first prompt. However, it took a little massaging to get things quite right. This is arguably due to unclear wording in the question itself, but that’s the point: how would you know? Beyond this, the commenting of the code itself wasn’t quite right, which doesn’t inspire confidence! I think this leads to a fun game, of sorts, in which you propose a problem statement, ask an AI to generate a solution, test it, and refine the prompt. There’s a lot to learn about how these generative AI systems work, about how we can leverage them in healthy ways, and, perhaps most importantly, about the limitations of these systems and how the “human-in-the-loop” is expected to contribute to the solution.
]]>If we have a VarIOT ThingsBoard gateway (which can be installed on a Raspberry Pi), we can connect it with a ThingsBoard server (one of which is hosted at Drexel Univesrity) to send telemetry data from IoT sensors that we can collect and evaluate using a micro:bit. For example, we could send environmental data like temperature or humidity, or biomedical data like one’s respiratory rate (or anything else you can imagine!).
You can install VarIOT and the ThingsBoard software on a Raspberry Pi by following these instructions. These steps also describe how to connect a gateway to a ThingsBoard server.
I edited the ~/thingsboard-gateway/thingsboard-gateway/thingsboard_gateway/config/tb-gateway.yaml
configuration file on my Raspberry Pi and un-commented the code in the REST block to allow REST access from the micro:bit.
ThingsBoard provides an article describing how to configure the gateway and, specifically, how to configure RESTful access. You can add your own endpoints here following the template provided in the ~/thingsboard-gateway/thingsboard-gateway/thingsboard_gateway/config/rest.json
file.
One thing that is required is to change the host name from 127.0.0.1
to 0.0.0.0
so that the endpoints are available from the outside world. I did not enable SSL, since I don’t have SSL built into the code of the HTTP extension driver. For this reason, I’m also not using a username or password, since these would be sent in clear text, but these are prototyping practices only (and not for production use!).
If needed, you can VPN to the ThingsBoard server by running: sudo openconnect -u <your user name> -b <vpn server name>
on your Gateway Raspberry Pi. This will allow your gateway to connect to a server that is behind a VPN.
In my rest.json
file, I see that I have an endpoint called my_devices
. This is not the most descriptive name, since it’s just a template, but rather than modify it, I’ll use it for this demo so that we have a proof of concept with minimal configuration effort. We can test this endpoint using a curl command from a terminal:
curl http://rpi4-variot:5000/my_devices -H "Content-Type: application/json" -X POST --data '{"temp": 25, "sensorName": "Mongan Gateway", "sensorType": "default", "sensorModel": "testmodel"}'
This endpoint is configured to map the temp
parameter to the temperature
telemetry value. For fun, I also send an attribute called model
that is mapped to the sensorModel
parameter in the configuration, and I will send the value testmodel
. This can be left out. When I configured my Gateway, I called my gateway sensor Mongan Gateway
, and so I will configure this as the sensorName
as well.
Next, we will connect the micro:bit to WiFi so that we can connect to the Internet and send telemetry data to the gateway. The instructions and diagrams provided by these instructions for a ThingSpeak board are exactly what we need. I did not bother connecting an LED or the USB, so only the connections to the ESP8266 WiFi module was needed.
I used a breadboard, jumper wires, and an ESP8266 WiFi Module to do this, as well as a breadboard power supply. I also used a micro:bit breakout board so that I can connect jumper cables to the pins of the micro:bit from the breadboard, rather than needing to use the allegator clips to clamp to the metal pads. I’ll include links to the items I used.
I connected the Rx pin of the WiFi module to P0 of the micro:bit, the Tx pin to P1, VCC and CH_EN to the 3V pin for power, and ground to the ground pin, as shown in the diagrams of the wiring diagram and pinout (shown below; image credit Alan Krantas via the alankrantis/pxt-ESP8266_ThingSpeak GitHub repository under an MIT License):
Following these diagrams, I wired my WiFi module to the micro:bit as follows:
Finally, we can import this extension into a micro:bit project. To do this, create a micro:bit project at makecode.microbit.org and click the settings cog at the top right. Choose Extensions
, and search for VarIOT. Alternatively, you can import this URL directly to import the extension from GitHub: https://github.com/BillJr99/pxt-ESP8266_VarIOT.
That’s it! You can now use VarIOT blocks to send data to the VarIOT gateway. There are three basic steps (and blocks) to do this:
on start
: add an Initialize ESP8266
block. The RX and TX pins should be set to P0 and P1, respectively (this seems backwards from the pinout wiring we did earlier, and that’s OK, because the Rx of the WiFi is the Tx of the micro:bit, and vice-versa!). Set your WiFi SSID and password here.on start
, but after connecting to WiFi, add a Configure VarIOT gateway location
block and set the IP address and port of the VarIOT gateway. For me, that is rpi4-variot
and 5000
, but this will depend on the IP address of the Raspberry Pi and the port used when configuring the gateway above.Upload data to VarIOT
block and fill in the details. For me, I used the following settings from when I configured the gateway:
Endpoint
: my_devicesDevice Name
: Mongan GatewayLabel
: tempValue
: 55That should do it! When you view your gateway dashboard on the server, you should see this value appear. Here is the code:
You don’t have to do this step, but if you’re interested, this section describes how I created the micro:bit extension blocks for use in your micro:bit program (to connect to VarIOT).
To do this, I forked the alankrantas/pxt-ESP8266_ThingSpeak repository into my own extension, which I call BillJr99/pxt-ESP8266_VarIOT, under an MIT license.
All of the original repository’s WiFi connection and TCP connection code applies here, so I was able to re-use that. In effect, we will run our cURL command that we saw above to send telemetry data to the Gateway, but we’ll get these values from a micro:bit! I moved this code into a function called doHTTP
, and created blocks that send an HTTP request to post data from the micro:bit as follows:
/**
* Connect to VarIOT and upload data. It would not upload anything if it failed to connect to Wifi or VarIOT.
*/
//% block="Upload data to VarIOT|Endpoint = %endpoint|Label = %label|Value = %value"
//% endpoint.defl=mongan
//% label.defl=temp
//% value.defl=45
export function sendVarIOTTelemetry(endpoint: string, label: string, value: number) {
let body: string = "{\"" + label + "\": " + value + "}"
let str: string = "POST /" + endpoint + " HTTP/1.1\r\n" + "Content-Type: application/json" + "\r\n" + "Content-Length: " + body.length + "\r\n\r\n" + body + "\r\n\r\n"
doHTTP(str)
}
/**
* Connect to VarIOT and upload data given a device name. It would not upload anything if it failed to connect to Wifi or VarIOT.
*/
//% block="Upload data to VarIOT|Endpoint = %endpoint|Device Name = %devicename|Label = %label|Value = %value"
//% endpoint.defl=mongan
//% devicename.defl="Mongan Gateway"
//% label.defl=temp
//% value.defl=45
export function sendVarIOTTelemetryByDeviceName(endpoint: string, devicename: string, label: string, value: number) {
let body: string = "{\"" + label + "\": " + value + ", \"sensorName\": \"" + devicename + "\"}"
let str: string = "POST /" + endpoint + " HTTP/1.1\r\n" + "Content-Type: application/json" + "\r\n" + "Content-Length: " + body.length + "\r\n\r\n" + body + "\r\n\r\n"
doHTTP(str)
}
/**
* Configure VarIOT gateway location
*/
//% block="Configure VarIOT gateway location|URL/IP = %ip|Port = %port"
//% ip.defl=rpi4-variot
//% port.defl=5000
export function configureVarIOT(ip: string, port: string) {
variot_configured = true
variot_ip = ip
variot_port = port
}
We will sample some tools that have been developed and used in Southeastern Pennsylvania to enable students to explore computing concepts and discover solutions to authentic problems, with an aim toward building their confidence in creatively applying technology to workforce-relevant application domains. We will connect these tools to potential careers and career pathways, and conclude with a survey of industry-grade no-code (or ubiquitous-code) platforms for further exploration.
Some problems are hard to approach because we lack the complete worldview required to solve them entirely. These are problems that are larger than any one of us, and require collaborative and often multidisciplinary teams. There is no “perfect” textbook solution, and so an iterative cycle of planning, trial, evaluation, and feedback is needed. These authentic problems provide opportunities to develop and apply design thinking skills.
Design Thinking seeks to understand the problem domain, which requires discussion with a variety of stakeholders [1, 2]. We’re not likely to know all the right stakeholder groups until we “live and learn,” and so this is an iterative and perhaps never-ending process. One way to approach this is to experiment and prototype with potential solutions, and then “shop them” to your stakeholder groups for feedback. Learning a new concept, whether it is a lesson on the Federalist Papers, the Pythagorean Theorem, or computer programming, can be considered an opportunity to learn and explore in-context through the Design Thinking process. No-code platforms provide a mechanism to express those designs in an automated and collaborative environment. In essence, we are applying inquiry and discovery learning through the use of technology platforms that are accessible to students across any discipline (in other words, not just the computer class). By viewing problems through the lense of the stakeholder, we bring empathy into the problem space, incorporate a “backwards design” by approaching problems from an outcomes perspective, and facilitate creative innovation in the classroom setting through role playing activities.
This approach enables collaboration across different classroom environments - not just between the STEM students and the context disciplines, but between entire environments. For example, a rural school district familiar with regional challenges (for example, an agricultural or healthcare need) could engage with suburban and urban school districts with their own regional challenges (for example, traffic or environmental needs) to exchange broader perspectives and diversity of thought. Often, the technology and platforms required to approach these challenges is similar across these problem spaces, and exchanging common ideas creates new pathways to collaboration as well as a diversification of perspectives.
If you’ve ever lost your backpack, glasses, or other items, you might be aware of tools designed to help find those items by playing a sound and letting you see how close you are to the missing item. By listening to the sound, you can track down where the missing item is. As you get closer to the source, the sound becomes louder. Sometimes, devices like these emit a radio signal - not an audible sound, but an invisible wave of light, that computers can sense with a radio antenna. As you get closer to the source of the signal, the intensity of the signal increases, just like the sound did. Using the BBC Microbit no-code platform, we will develop a system to communicate a radio signal between two devices to play a game of “hide and seek” where the goal is to locate the source of the invisible signal [3].
First, students should plan a solution together: what steps are required to play a game of Marco Polo? Without requiring a technical background, some insights can emerge: every so often, the “source” generates a signal that the players can hear (but can’t see); this sound has a direction and intensity that helps the players move closer to the source. As this repeats, if the sound becomes louder, we can infer that we are getting closer.
Let’s make a flow-chart of this idea and lay out the building blocks in the software environment.
We can build each of these steps in the Microbit Makecode environment [3]. We will build this from scratch, but you can import it by clicking the Import button and importing from this URL: https://github.com/billjr99/hide-and-seek. This environment includes a simulator that will run the project without physical hardware, but if you have the devices, they can be downloaded to a physical Microbit by clicking the three dots next to the Download button, and choosing “Connect to Device” from the menu that appears. You’ll need two divices for this project (one to “hide” and one to “seek”).
The project opens with two “functions” or slots that we can fill in with behaviors: “on start” and “forever.” The “on start” function will allow us to set up items that we’d like to keep track of during the program, while the “forever” function will dictate what happens over and over. From the flowchart, we can see that everything happens in this “over and over” loop - and is kicked off by sending a signal to our partner. Under the “Radio” section of blocks on the left, there is a “radio send number” behavior that we can drag into the “forever” function. We’re just measuring how loud the signal is: when playing “Marco Polo” the “signal” is to shout the word “Marco,” but in reality, it doesn’t matter what the word is! We’re really listening for the intensity of the sound. So, any number will do here.
In the “on start” section, use the “radio set group” to set what is called the “radio group” - this is like setting the channel on a walkie talkie so that each pair of radios can communicate in isolation. Choose any number here, as it will be the same for both devices.
Finally, if you’re using a real physical Microbit device, using the radio continuously in the “forever” function will drain your batteries very quickly. You can add a “pause” behavior in the “forever” loop to stop in between signals (imagine yelling “Marco” over and over continuously! It’s OK to put a little break in between). I put a 1000 millisecond pause into my loop, so it will send a signal every second. Feel free to adjust this as you like.
Whenever we receive a signal, we want to ask ourselves whether that signal is “louder” or “softer” than the last time we received it. To do this, we will introduce the concept of “variables.” These are values that we can remember over time. We’re interested in remembering the intensity or “received signal strength indicator” (RSSI) of the last wireless radio signal we received, so we’ll create a variable called lastRSSI
. In the “on start” function, add a “set variable” behavior and create this lastRSSI
variable. You can give it any starting value that you like, since it will update over time.
We’ll compare this value to the new signal each time we receive one. We’ll set the variable value to the current signal strength as well, so that next time, we can compare that, too. This is like remembering in your mind how loud the “Marco” shout was the last time you heard it, and comparing it to what you’re hearing now. Next time, you’ll compare it to what you just heard, and so on.
You can drag other functions into the canvas; under the “Radio” section, there is a function called “on radio received.” This function will execute any time a new signal is received. It comes with its own variable (known as a “parameter”), called receivedNumber
. This is the number that you sent during the “radio send number” behavior. We don’t really need it: it doesn’t matter what the signal was (“Marco” or “Hello” or anything else!), but rather how loud it was!
On our flowchart, we want to ask whether this signal is louder or softer than the last one. The current signal strength can be found in the “received packet: signal strength” behavior under the “Radio” section on the left. Since we’re asking a question about this value, we can use the “if - else” behavior found under the “Logic” section. In the if block, drag the “X > Y” behavior from the “Logic” section and place it inside the diamond shaped space of the if behavior. Inside that, you can place the “received packet: signal strength” value, and the lastRSSI
variable.
If the current signal strength is greater than the last one, we’re getting closer! I used the “show arrow: North” behavior to show this on the LED display, and dragged that inside the if block. In the else block, we know that the signal is not stronger, so it is either weaker or the same. I used the “show arrow: South” behavior to show this, and dragged it into the else portion of the if behavior.
Finally, after the if block, I added a “set lastRSSI
to” behavior. We want to set this variable to the current signal strength. We checked this value just a moment ago, and itw as called “received packet: signal strength.” Let’s set lastRSSI
to the received packet signal strength.
And that’s it! If you run this, you should see arrows on the two devices showing if they are getting “warmer” or “colder.” With physical devices, you can walk around to see this in action. On the simulator, there is a square “wave” in the top right simulating the radio activity. You can click and drag left or right to increase or decrease the signal strength, and you should see the arrows update accordingly.
The if behavior has a cousin called the “if - else if - else” block. This allows us to ask multiple questions in the same if statement. Previously, we asked if the signal was stronger than the previous one, and showed a “North” arrow if it was, and a “South” arrow if it was not. But what if the signals are equal? This if statement can ask if the new signal strength is stronger, and otherwise (“else”) ask if the new signal strength is weaker. In the final “else,” we now know that the signal is neither stronger nor weaker, and must be the same. I displayed a heart icon on the LED display if this was the case, indicating that we’re not getting closer or farther from the target right now.
You may have seen the concept of RSSI when you choose a wireless network on your phone or computer. Often, you’ll see the classic “bars” of signal strength, which is really just a visualization of the signal strength number that we’ve been comparing in this program. To a computer, everything is a number, and sometimes, you might see the actual number on your computer or on your phone. It’s measured in decibels, and these radio signals are often pretty weak (likely a numeric value between -40 and -100). It’s still amazing to me that these faint invisible signals allow us to carry data over the internet, send music to our radios, and even communicate wirelessly around the world. Even right now, there could be dozens or hundreds (or more!) waves of invisible light energy bouncing around you carrying wireless data by radio signal.
Let’s add a variable to store this value each time we receive a signal. Based on what we’ve done already, where should we create this variable, and when should we update it?
Once you’ve added this variable to the “on start” function and set its value each time the “on radio received” function executes, you can drag another function to the canvas called “on button pressed” that we will use to show the current signal strength value whenever the A button is pressed. Its behavior is just a single step: show the currentRSSI
variable value.
Finally, if you’d like a whole class to use this at once, it is helpful to put each pair of radios into a unique radio group, so that two people can play together without “hearing” and displaying the signals from everyone else’s device. To do this, you can change the number in the “radio set group” behavior to a unique value for each pair of devices. Although that change alone is sufficient, I created a variable called radioNumber
to do this, just for fun. I had my radio send the radio group number in the “forever” loop, and added an if statement in the “on radio received” function to check that the receivedNumber
parameter was equal to the radioNumber
variable before checking the signal strength - almost like a little passcode to help ensure that the radio is hearing the correct partner.
Here’s my finished product:
The signal strength will vary based on the distance between the two devices, although walls and other items in the room will also cause the signal strength to change. Roughly speaking, though, the signal strength can be thought of as a proxy to the distance, just like the number of “bars” of signal strength is a rough estimate of how far you are from the cell phone tower or wireless access point (although other artifacts, such as being indoors, will also lower the signal strength and make it appear that you are even farther away).
Older or more experienced students could display the RSSI at a few known distances from their partner, and fit a linear function to those points to get a rough estimate of how changes in the signal strength correspond to distance. Admittedly, the relationship is not truly linear and there are other variables involved, but it is a reasonable estimate using things we can measure.
Add a variable to the “on radio received” function that calculates the distance using the linear equation on the signal strength. Change the “on button pressed” function to display this value instead!
During the pandemic, my colleague Chris Tralie developed a Javascript system that allows students to write code in their web browser, which is transpiled to JavaScript from their language of choice (for example, Java and Python) or compiles to WebAssembly and executes in the browser (for example, C++). We connected these modules to our Learning Management System to assign class warmup exercises that are autograded, and that provide “hints” through automated feedback about incorrect answers.
Using Dr. Tralie’s framework, I added support for the R statistical processing language [4] and the SQL database language.
The addition of R and SQL allows students to experiment with database management and data analysis without requiring software installations on their local computer. We will explore the classic Iris flower dataset from R.A. Fisher in 1936 using R [5].
Imagine holding some coins in your hand. Can you tell what they are without looking at them? How can you tell? You might look for some common “features” like the thickness or the diameter of the coin, or the weight, or even the ridges along the outside of the coin. If the coin is in rough shape, there might be some variance in the data that causes you to guess incorrectly from time to time, but the idea is that we can predict a classification of data by evaluating some known features against examples we’ve seen in the past.
We will use the Iris flower dataset to predict the species of a flower using a few features such as the length and width of the petal. To try this out, navigate to this flower classifier page which is embedded below[6]:
Fill in some numbers for the petal length, petal width, and sepal length for some imaginary flowers. You can select the species for each flower click “Add” each time to plot the flowers by species on the 3D plot (which you can click to rotate and zoom). Take a look at the plot: do your flowers group in a way that makes sense? If the species data points are far away from each other when the species is different, but close together with those of the same species, there’s a good chance you can classify a “mystery flower” between these species choices. Enter numbers for a “mystery flower,” select the species that you think it is, and click “Guess” to see if you can correctly predict them! If not, try refreshing the page, and adding new example flowers whose measurements “group together” by species a bit more closely. Do you have an easier time classifying between them now?
R.A. Fisher’s Iris flower data is built into the R platform, and we have embedded an R webassembly compiler [7] into a webpage [4] for exploration in our courses that use the R language (embedded below).
We will begin by viewing the the Iris dataset for analysis, which is described in detail in this article [5]. To do this, you can type this command into the R console: head(iris)
, which displays the first few rows of the dataset. It contains columns for the sepal length and width, and the petal length and width, as well as the species of that flower. There are 50 Setosa flowers, 50 Versicolor flowers, and 50 Virginica flowers in the example dataset.
The species column is not numeric, so we can’t plot that. However, we will use the species later to color code our plots. For now, let’s extract the first four columns (the numeric features) into a “dataframe” variable, by typing: df <- iris[, 1:4]
.
You can graph the columns of this dataset against each other. We’ll use two features at a time so we can more easily view the data on a 2D plot. You can do this by typing: plot(iris$Petal.Length, iris$Petal.Width, col=iris$Species)
, which plots the petal length against the petal width, and color codes them by species. To see which color represents which species, you can add a legend to the plot by typing: legend("topleft", levels(iris$Species), fill=1:3)
. How well do you think the Petal Length and Petal Width can help you predict the species of a flower, and why?
Run these commands to see 2D plots of all of the columns against each other (this is called a “pairs plot”). Type this command to generate it: pairs(df, col = rainbow(3)[speciesID], labels=c("PetalLength", "PetalWidth", "SepalLength", "SepalWidth"))
. Which features do the best job separating the flowers by species?
Another free platform that I have come to like for web-based pair programming in a variety of languages is replit. I wrote an article describing some helpful tools built into replit, including external library support, unit testing, version control, basic graphics, and a data store [8].
I work with a broadly multidisciplinary team on developing textile-based wearable computing systems. Imagine if the “radio” you experimented with earlier using the Microbit could be embedded into your shirt, or into a baby’s onesie, and it was so small that you didn’t know you were wearing it! These ubiquitous computing systems are generally known as the “Internet of Things” (IoT), and are interconnected smart devices that we can use to measure physiological functions like heart or respiratory rate, environmental measurements like air quality, or highway sensors for traffic management. Here, computer scientists team up with physicians, sociologists, designers, engineers, and more, to create solutions that no one group could do on their own. My colleagues at Drexel University run a program using the Vertically Integrated Projects model to provide opportunities for students across majors to blend their classroom experience with authentic problem solving and research skills. In my faculty role at Ursinus College, my students and Drexel’s students collaborate virtually, develop solutions, conduct experiments, and present results, by bringing their own unique classroom and major experiences to the team.
The National Science Foundation (NSF) Science Nation series featured Drexel’s smart fabric Bellyband and the team. The video can be found by clicking below:
By approaching computing education from a Design Thinking perspective, we can reinforce the “human in the loop” and promote empathetic design choices early in the educational journey. However, this approach also enables viewing problems through a broader lens in which technology is a means rather than an end. Authentic problems can be shared from industry partners, who can participate in a low-committment way by serving as project stakeholders, providing user perspectives to students who can work in multidisciplinary teams to develop solutions.
Another benefit to this approach is that it celebrates the variety of skills inherent in computing; we often think of “programming” as the fundamental skill of computing. And although computer programming is a ubiquitious tool that should be universally understood to facilitate communication about how the computer solves problems, it is not necessary to write code as part of a computing career. One could imagine software design as a core aspect of problem solving with technology, by designing user-centric workflows and determining the basic steps and modules required within a software system, or user experience (UX) design, which centers on interfacing with humans to develop systems that are optimally usable within the constraints of our physiology and psychology, or quality assurance and software testing which plans how to validate that software works correctly, and identifies deficiencies in the software. Information Technology (IT) specialists and systems administrators (and database administrators) ensure that the operational aspects of managing and maintaining the development environment, and front end developers create graphical user interfaces and web-based designs that allow one to engage with the software. Project Managers coordinate the team and ensure that these groups are collaborating efficiently. On top of this, each of these experts must also coordinate with domain experts who understand the target applications of computing systems. All of these people work together to solve problems collaboratively, and each brings a unique and essential skillset to the team. Just as the Vertically Integrated Projects model allows for the integration of and collaboration among a diverse set of technological and domain skillsets, computing and design thinking allow and encourage people to consider problems that are “bigger than themselves.”
You can find out more about these career paths and more here!
https://www.billmongan.com/Ursinus-CS474-Spring2022/Activities/DesignThinking ↩
https://public-images.interaction-design.org/literature/articles/heros/5808b55608af6.jpg?tr=w-1024 ↩
https://www.billmongan.com/Ursinus-CS173-Spring2022/assets/js/R/TutorialExercise ↩ ↩2
https://gexijin.github.io/learnR/step-into-r-programmingthe-iris-flower-dataset.html ↩ ↩2
https://www.billmongan.com/Ursinus-CS173-Spring2022/Activities/FlowerClassifier ↩
https://github.com/georgestagg/webR ↩
https://www.billmongan.com/posts/2021/03/replitclassroom/ ↩
Using Replit, you can add external library support with many languages. For example, if you import a package, Replit will automatically download and import the corresponding library when you compile or run the project. However, if you have your own custom library that you’d like to import, you can do this as well.
If you are using a Java project, you can upload a jar file to your project (in my example, I uploaded a file called rsamath.jar to a directory called lib
). You will have to tell Replit how to compile your project with this library in your CLASSPATH
, and we can do this by creating a configuration file within the project called .replit
. Add the following text to this file [1]:
run = "export CLASSPATH=\".:lib/rsamath.jar\"; javac -d . Main.java; java Main"
This adds the jar to your classpath along with the current project directory, so that your Main.java can compile (in the main project directory) while also loading the library from the rsamath.jar file. The remaining javac and java commands are the standard compilation and execution commands that replit would use by default.
Here is an example project [2] that computes a value and its modular inverse from prime number inputs:
While we’re at it, Replit provides a key/value data store to each project that we can use to store those generated keys, in case you’d like some values to persist in between runs of a student’s project. Replit provides a few web API endpoints via an environment variable that it maintains for your project, including:
GET $REPL_DB_URL
: Insert a key and value into the datastore. The data passed with the body of this HTTP request is key=value
.GET $REPL_DB_URL/key
: Retrieve the value associated with the key.DELETE $REPL_DB_URL/key
: Delete the key from the datastore.I have encapsulated these web calls [3] into a class called ReplDb
that are provided in the following example project:
Replit maintains a history of the revisions made to each project, but integrating with git provides an opportunity to teach the fundamentals and mechanics of version control. Under the Version Control left menu of the Replit project page, you can create a git repository as follows:
When the repository is created, you will see something like this:
As you revise the project, you’ll have an opportunity to click to create a new revision, including a log message, that you can use to revert your project to any commit that you’ve made.
Finally, you can connect this repository to your GitHub account. This may not be appropriate for homework assignments so that they aren’t inadvertantly made public (more on this below!), but is a nice way to create a public portfolio of independent study projects and final course projects.
Replit supports graphical displays through the browser! You can download Robert Sedgewick’s algs4.jar library [4] and add it to your project as we described above. Running the project will display the output window as a frame within your web browser.
Here is an example project demonstrating this [5].
You can use animations with this library as well! It’s a little slow through the browser, but you can clear the screen and draw lines iteratively to create fun animations. Here’s an example:
We will see later that you can add your own unit tests and input/output tests in order to autograde a Replit project, but you can encourage students to create their own unit tests, too.
Click the checkmark on the left menu, and click the setup button to initialize your tests. It will prompt you to add these imports:
import org.junit.Before;
import org.junit.After;
import org.junit.Test;
import static org.junit.Assert.*;
Then, click “Add Test” under the same checkmark menu. Add your test code, for example:
We will create the following test:
Main tester = new Main();
int result = tester.squareIt(-2);
assertEquals(4, result);
Finally, click Run Tests under the checkmark menu for a report. I have not found a way to export these tests for submission, but you could ask students to copy their test code snippets and a screenshot of their report for a low-tech solution.
Here is a quick example project demonstrating how to create and run a unit test:
If you are using a custom .replit
file with external libraries, you can add this jar to your CLASSPATH
in your .replit
project: /run_dir/junit-4.12.jar
.
My students often miss the “auto-format” button on their Replit projects. This is a nice way to clean up things like indentation, and to demonstrate best practices. The button is located on the top right of the code window for the project.
Another often overlooked feature is project sharing functionality. Click the “Share” button on the top left of the Replit project to invite other users to pair program with you simultaneously. Note that this button might disappear if an ad-blocker plugin is used on the browser.
In my classes, I use a POGIL style method to facilitate class discussion. Instead of lecture notes, each class features some guiding questions, as well as some examples or materials to spark some curiosity and discussion. As you have seen in this article, I also embed Replit project examples that students can view, fork, modify, and share with me from within the activity page. You can do this by embedding an iframe
in your web pages with a src
tag indicating your Replit project URL (with ?lite=true
at the end of the URL), as follows:
<iframe height="600px" width="100%" src="https://repl.it/@BillJr99/MiniCrypto?lite=true" scrolling="no" frameborder="no" allowtransparency="true" allowfullscreen="true" sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-modals"></iframe>
There is a button at the top right of the frame to open the example in a new browser tab. Here is an example lesson.
In addition to creating a version control repository, you can configure your assignments to automatically create repositories linked to your students’ Replit projects that are shared with you for grading and auto-grading. One nice feature of GitHub Classroom integration is that you can configure these repositories to be private (although making the underlying Replit projects private may require a paid subscription). I created a short video demonstrating how to set this up in a prior article.
For a more detailed introduction to git, see this book.
SSH Keys allow you to present a certificate to the GitHub server that authenticates your user account from your computer. It is an alternative to password-based authentication. You can create an SSH key, which will create two files on your “home directory” called .ssh/id_rsa
and .ssh/id_rsa.pub
. The .ssh/id_rsa.pub
file contains your public key and is the file you will share with GitHub. The private key in the other file corresponds to your public key, and, as the name implies, should not be shared with anyone (as it will enable authentication against the public key).
Once the key is created, you can copy the public key text and paste it on the GitHub website to add it. It’s a good idea to name the key, in case you get a new computer and start a new key; it’s a good practice to remove unused public keys from GitHub and other servers.
If you are using TortoiseGit, you can use a tool to manage your SSH keys from Windows. See this article for details.
Cloning the repository downloads it to your local computer. You can do this as many times as you wish, and synchronize across them. You can even share the repository with other users, and they can operate on the repository locally as well.
Sometimes, you’ll have private files in your repository directory that are important, but too senstive (or just custom to each local computer) to save to the remote repository in the cloud. For this reason, you’ll explicitly add each file that you want to upload to the cloud. Note that you also add files that you’ve modified even if they already exist.
Once you’ve added your file(s), you can commit your changes to the repository. This creates a log timestamp that you can see in the repository to track who did what and when. You can even roll back your repository to any point in time marked by one of these commits. It’s a good idea to commit relatively often, whenever a major milestone is reached that you might like to revert to or review someday. It’s also a good idea to specify a commit log message so that these commits make sense beyond simply what files were modified and how. This is specified with the -m
flag to git commit
.
Before git came along, repositories would often automatically sync to the remote in the cloud as soon as a commit was made. This might even seem reasonable at first glance. But not all computers are connected to the Internet at all times, and so it is helpful to separate these operations so that you can make commits while offline. After you have made one or more commits, you can push them to the remote in the cloud.
In addition to adding files, you can also remove files from the repository with git rm
. You’ll commit and push these changes just like with add operations.
One final note: it’s a good practice to pull the repository from the remote cloud before performing a push operation. In fact, it’s required if anyone else has pushed commits that you have not yet downloaded. You can use the git pull
command to do this, and should plan on doing this at least any time you push.
Things can get messy if there are many people working on many different parts of a repository with different objectives in mind. It is nice to have a “sandbox” to work in that is separate from the rest of the team, and then to merge that sandbox back into the master repository when you are finished. These “sandboxes” are called branches. You can create a branch using the git branch <branch name>
command, and switch between branches using the git checkout <branch name>
command. Git provides a shortcut when creating a new branch that executes both operations: git checkout -b <branch name>
.
Initially, the branch will be identical to the current branch, but it will be on its own independent commit timeline.
You can commit and push to your branch like before. When pushing to (or pulling from) a particular branch, you can specify the branch to git via git push <remote> <branch name>
. By default, the GitHub remote is called origin
, so you would enter git push origin <branch name>
. You can have more than one remote, which would allow you to sync with multiple remote servers or to create custom actions to occur when you push commits. This is beyond the scope of this article as we won’t need it for GitHub classroom, but if you’re interested, you can find out more here.
With GitHub, it’s possible that a single user or subset of users are in charge of the master branch, into which these branches would often be merged. To request a code review and merge of a branch, you can create a Pull Request that seeks a review, comments, and ultimately a merge of the branch.
With GitHub classroom, branches and Pull Requests are useful because they allow students to communicate with instructors about their repository. Students can seek assistance on their code by creating a Pull Request on a branch, and instructors can use the review feature to comment on the student’s attempt down to the line level. However, this is also a good practice for using git and GitHub, and GitHub classroom provides a convenient opportunity to practice these operations in ways that will help the student get assistance on their coursework. More on this later.
You can merge a branch into your existing branch using the git merge <branch name>
operation. You can checkout the target branch first, and then merge the other branch in.
Notice that you do not need to commit after performing a merge: the merge is a commit on your current branch. If you perform a git commit
, it will provide you a helpful reminder about this. Instead, you can go ahead and push to the remote GitHub server when you are ready.
Sometimes, team members make changes to the same parts of the same files in different branches (or from different local checkouts). A conflict occurs when this happens. Git offers you some guidance in how to resolve these, and allows you to specify the details as a new commit. You can find more details on how to do this here.
]]>Because GitHub classroom is built on GitHub and git infrastructure, it is important to understand some git and GitHub basics before using GitHub classroom. You and your students can get started with a working knowledge of just a few features, and we’ll summarize them here. Before starting, you should have the following:
The basic workflow of GitHub classroom allows you to create a boilerplate repository that contains your assignment starter code and instructions, and to associate that boilerplate repository with an assignment. The assignment is then cloned as individual student repositories by your class, without requiring you to coordinate all the repositories and which students have which repositories. The workflow looks like this:
GitHub in the classroom provides a number of benefits with minimal setup requirements that you can re-use from class to class. These benefits include:
If you already have a GitHub Organization, you can skip this part and go directly to creating a new classroom under that organization. You only have to create an organization once.
GitHub Classroom uses Organizations to manage your classrooms, assignments, and student repositories. You and your TA’s (and co-instructors) will be members of your organization (they’ll be added throughout the process, so you don’t have to do this all yourself). In addition, student repositories will be incorporated into your organization when they accept assignments, and you can provide roster information whereby students can associate themselves with their GitHub account. When I use the student roster, I like to use aliases that I share with my students (or that they share with me) so that no personal information is actually divulged.
If you are within an academic institution, you can petition GitHub to make your organization a “Pro” organization free of charge. This enables you to create assignments for which student repositories will be private. This is really important to me, because I don’t want independent student classwork to be made public! In this example, I’m just creating a personal organization, but this means that the repositories will be public. As an educator, you can apply for GitHub Education benefits here by clicking “Get Benefits” at the top right.
Your TA and instructional team will be administrator members of your organization, so they will have access across your classrooms. Because of this, I tend to prune the list of instructional staff each semester.
You will be given invite links for students and for TAs to add themselves to your organization and classroom.
Pro Tip: I suggest creating your roster first before sending these out, so that students can associate themselves with student ID’s (assuming you haven’t automatically populated these from your LMS).
If you specify a starter code repository, that repository will be copied into each student’s repository when they accept the assignment. You can specify a deadline as well. If you do this, the Classroom Assistant tool will download the repository as it was at that time. No commits after the deadline will be seen. You can even push to the student repositories as a grading mechanism. I tend not to do this, so as to avoid posting grading information to a potentially public forum, but it’s useful for private repositories. In addition to creating an individual assignment, you can create a group assignment. When students accept an assignment, they will be able to organize into a team of up to N students (a number you can specify). You can also re-use existing groupings to keep teams together.
Pro Tip: I like to create boilerplate “starter” assignments to house my instructions. By storing these in README.md
, the GitHub repository page will show them to your students when they natigate there, so I can link directly to the repository page from my course page. You can also include starter work like template code. This way, I can focus instead on evolving the assignments! This is useful for instructions, boilerplate materials, FAQ materials, and rubrics.
Any repository can be a “starter repository” since you’ll select it when you create the assignment. Just be sure to set the starter repository to a “Template Repository” which GitHub uses to copy the repository to the student. You can do this retroactively under the repository “Settings” menu, under the main “Options” pane.
Pro Tip: I suggest that you do not make students administrators on these accounts, so that they can not modify their sharing settings on the repository. This helps ensure that they do not accidentally make their repository public.
Pro Tip: The repository names will start off with the name of the assignment (i.e., assignment1-ABC123
). I like to set my assignment names/classroom names/assignment prefixes with the current semester/year (csta-spring-2020-assignment1
). This makes it easier for me to quickly filter by class, section, and year. Since GitHub Classroom names the repositories, this is much more convenient than asking students to name their repositories according to a schema (which some may forget to do!).
When students accept an assignment, you’ll have a record of it, and they will get a repository that is shared with you and the TA’s (and any students in their group, if you created a group assignment).
The first time a student accepts an assignment, they’ll be able to assign themselves to a student in your roster. This allows you to associate a student’s GitHub account name to their actual student identifier.
I tend not to link my GitHub Classrooms with a school roster or LMS, so that I do not risk compromising sensitive student data. I allow them to self-associate. There is a risk here that a student will mistakenly identify with another account. If this happens, that student will not be able to associate with their account. In practice, I have not had a problem with this, but you’ll want to ensure that students are representing themselves properly if you’re allowing them to self-assign their account to a student on the roster.
Once the student has accepted the assignment, they can work with it like a normal git repository. GitHub Classroom saves you the effort of asking students to configure their own repositories, share it with their team members, make the repository private, share it with instructors, etc.
When it’s time to pull all the repositories, you can use the GitHub Classroom Assistant to identify and clone all the repositories associated with your assignment. You can also access their repositories directly and push grading information or feedback once they are cloned (or via the GitHub web interface).
Note that the link you give to GitHub Classroom Assistant is the web link of your GitHub Classroom Assignment page - it’s not the git link itself nor the invite link.
Pro Tip: I have Two Factor Authentication enabled on my github account, so I can’t check in files that GitHub Classroom Assistant downloads. Below, I’ll share links to scripts that will automate checking out classroom repositories using your SSH public key, which will enable pushing to the repositories without authenticating with a password.
You can push directly to the student’s repository for them, or you can create a file on-the-fly through the repository webpage. You can click “Create New File” to create a text file, which you can save, commit, and push from the web interface.
If students work in a branch, they can make Pull Requests on that branch to request reviews and comments from the instructor along the way. This is a great way to ask questions and seek help.
Students can specify which instructor(s) (or team members) to specify as reviewers; they’ll get a notification to view the Pull Request.
Pull Requests can be found under the “Pull Requests” menu of the repository page.
You and the student can continue commenting on the Pull Request until the issue is resolved. Meanwhile, the student can continue committing to the branch, so that any “broken code” does not corrupt their master branch while they collaborate with you.
Ensure that students remember to merge their pull request (and, ultimately, their branch) into the master branch. This will make it easier for you to find their work when you download their assignments. By default, you’ll see the master branch, and that will be empty if they only worked in an unmerged branch. So, although branches are really useful in practice and to generate Pull Requests to get help, it’s also helpful to merge them back into master
before submitting. My students sometimes forget to do this, and I see an empty directory at first when I clone!
One drawback to using the GitHub Classroom Assistant to clone repositories is that, as far as I have seen, the Assistant uses HTTPS to clone the repositories. This makes it more difficult to push to the repository since password rather than public-key access was used. To authenticate with your SSH public key, you’ll want to use SSH to clone the repositories. These clone URLs begin with “git@github” as opposed to “https” and can be chosen from the GitHub repository homepage when cloning the repository, but GitHub Classroom Assistant does not appear to allow us to make this choice. You can use a shell script to download your repositories instead. This requires a Unix/Linux installation, a Mac with a shell terminal, or the Ubuntu Subsystem or a POSIX layer on Windows. I use Cygwin, personally, but these other environments should be equivalent.
GitHub user konzy created, and jfiksel forked, a mass_clone github clone script. Cloning this repository provides a clone_all.sh
script that will identify all your assignments and clone them over SSH using your public key (so you don’t have to enter a password!). It is run as follows, from the directory in which you’d like your cloned repositories to appear:
./clone_all.sh <Your GitHub Organization Name> <Assignment Identifier> <Your GitHub Username> ssh
The script will prompt you at the terminal for your password. Your GitHub organization is the name of the organization you used when you created your GitHub Classroom, and the Assignment Identifier is the portion of the URL that appears when you click on an assignment from the GitHub Classroom web page.
The ssh
parameter specifies that ssh links should be used; the scripts do allow for https
to be specified as the argument instead, and will clone using that protocol instead.
The push_all.sh
script is another useful one to upload your grading comments to all repositories with a single command. It will add all files you’ve created in the repository and commit/push them to the students’ repositories.
jfiksel also created some nice guides to using GitHub Classroom in a similar spirit to this guide here. There is an article for teachers and one for students.
If you are using two-factor authentication, you won’t be able to enter your usual password to the command line script. This is because there is no mechanism to execute the second factor, as you would with a web browser. GitHub allows you to create an app-specific password for this purpose: you’ll use it with your script, and no two-factor authentication is used. The disadvantage is that this bypasses two-factor authentication, but the passwords generated are often (hopefully!) stronger than a typical user password. Here is a tutorial to create an app-specific password on your GitHub account. You can assign repository permissions if you are only cloning/pushing to repositories, when it asks you for permissions on the app-specific password. It is a best practice to select only those permissions that are actually required of your app when using this password.
GitHub Classroom is a useful tool to teach git in the classroom, and to manage student work among instructional staff and among peer student groups. I’ve found a few tips and tricks in my workflow along the way, as you’ve seen above, but I’ve found this to help me manage student work in a meaningful industry platform. I like when students develop best practices while doing their work: often, we’re rushed to teach something and we forget to emphasize good habits along the way.
Here are some videos from the GitHub Classroom team that walk through many of the steps described in this guide, for your reference.
Check it out here - https://www.popqpodcast.com/season-3-episode-19
]]>