Assignment 14

Assignments

Individual Assignments

  • design, build, and connect wired or wireless node(s) with network or bus addresses

Group Assignments

  • Send a Message between two projects

Group Assignment

As im alone in Kamp-Lintfort from now on, I tried to come up with an Individual Assignment that doubles as a Group Assignment! Below you can read how I let two of my previous projects communicate with one another!


Individual Assignment(s)

For this week (and in part also for next weeks assignment), I wanted to realize a small project which I wanted to do for quite some time now. I wanted to visualize the current position of an in orbit object like the international space station (ISS) on a two dimensional map. Since I knew, that the current position of such objects could be gathered via specific APIs, I knew I would need some kind of wireless connectivity to gather the data and let one of my boards use it. Since I wanted to test out multiple connection systems at the same time, I came up with an Idea that would utilize network communication aswell as I2C communication at the same time. With this I would be able to learn how these tow different communication protocols worked and if it is even possible to use them like I want to :)

For now, the plan looked like follows:

  • To gather the positional data through periodic API calls, I wanted to use an ESP8266 Module
  • This data would then be read by one of my own Boards via Serial connection
  • The data would then be sent to one of my other Boards via I2C
  • Finally, the second Board would publish the data on a serial port to a python program, visualizing the current position on a map

As I was confused one or two times myself, I made a small picture to show what my plan looked like:

The plan I had in mind

As you can see, the plan looks not very complicated. However, I definetly did have my fair share of problems with this assignment. The most important thing to notice for now is, that TX and RX are not swapped for the ESP to Board 1 connection. This is not mistake, but rather intentional as the ESP module swaps them itself according to the datasheet. With this plan, I then created smaller working packages to do one after another. These were:

  • Getting an ESP8266, connect it to my Wifi at home and let it periodically request the ISS' positional data, sending it out via Serial
  • Creating a sketch that reads incoming Serial messages and sends them along further through I2C
  • Creating a sketch, that reads the I2C line and writes it to the Serial port on the PC
  • Creating a python project, that reads the incoming serial Data and uses it to showcase the ISS' currecnt position on a two dimensional map

As interface programming is part of next weeks assignment, I will disregard the last point for this documentation, as I am going to build this program next week. Later on the documentation about this program can (hopefully) be found here.

I then began working on the first Goal: Getting an ESP8266 and make it gather data through API calls. For this I bought this kit online, since I could not make it to the lab due to some personal stuff, forcing me to stay at home. After receiving the Modules, I noticed, that I would need a sodlering iron to attach the power boards that came with the set. Reading through the datasheet beforehand, I knew, that the chip would only take an input voltage of 3.3V, stating that higher Voltages could and possibly will damage the Board/Chip. I scoured my home for an alternative and in the end just used an Arduino Uno as some sort of power delivery with its 3.3V Output, well aware that the Amps could possibly not be enough to drive the ESP8266. But I wanted to try it at least. Following multiple tutorials and even an eBook that came with the Modules, I struggled for a few Hours to get the ESP8266 to work at all. In order to program it, I connected it according to this tutorial I found online. No matter what I did, however, the Arduino IDE did not seem to be able to connect to the ESP8266 in order to program it. I was majorly confused and scoured the web to find solutions. What surprised me the most, is that the serial communication, without uploading a sketch, worked fine. I could send AT commands without a single problem. The error I got when uploading sketches from however was the following:

					esptool.FatalError: Failed to connect to ESP8266: Invalid head of packet (0xF0)
				

After searching online, the problem was fixed with so many different solutions for different people, that I did not know what my problem was. No matter what I tried, I just was not able to get the IDE to program the ESP8266. This is, until I found out (the hard way -scouring the web for hours-), that the ESP8266 is best used with two buttons to control its internal modes. A classic reset button as well as a "program" button. Since I only was able to use breadboards at home, this would not be the most beutiful solution, but it could solve my problem. Just so other people do not make the same mistake, this Beginners Guide to the ESP8266 helped me soooo much. I can fully recommend it! With the ESP connected the way the Guide shows, I tried to upload the sketch once more, this time setting the ESP into programming mode first, through the use of the two buttons. One can chek if the ESP is in programming Mode by listening to Serial on the port of the ESP with a Baudrate of 74880. This is the ESPs Debug Baudrate. If reset correctly the ESP will display somethin along the line of boot mode: (x, y). If x is equal to 1, then the ESP is in program Mode. If its equal to three its in normal mode. With this one can quickly check if the reset circuitry functions correctly and if the ESP is ready to be programmed. To my dissapointment it still did not work. Sometimes the error code was the one above, sometimes it told me that the serial port could not be found at all, sometimes it said in the beginning, it was looking for port "COM4" and in the error MEssage below it said no device found on port "COM _". After almost giving up completly, I tried one last thing. Instead of using the Arduino UNO, I used my CatISP to program the chip. I just let the Arduino deliver power to the chip and let the CatISP handle the communication, since the CatISP only would deliver 5V instead of the needed 3.3V for the ESP. And for the first time I could finally upload a sketch to the ESP8266. I still dont know why I was not able to do it with my Arduino UNO, but I was more thatn happy that after all these hours of troubleshooting, I was finally able to uplaod a simpe, blink sketch I found online, which made the LED on the ESP8266 Board blink.

After that I searched for libraries and methods to generate API calls and receive data with the ESP8266 and found solutions very very quickly. I just edited one of the API call examples I found, added JSON functionality to it, in order to deserialize the data I would need and ignore all of the unneeded data to build the string I would be transmitting. The whole flashing process and the chip receiving data periodically can be seen in this short video:


The code used to program the ESP is the following:

						
#include <ESP8266WiFi.h> //Needed for the ESP8266
#include <Arduino_JSON.h> //Needed since API goves out JSON
#include <ESP8266HTTPClient.h> //Needed for the ESP8266
#include <WiFiClient.h> //Needed for the ESP8266

#define SSID "xxxxxxxx" //SSID that the ESP8266 will be trying to connect to
#define PASSWORD "yyyyyyyyyyyyyyyy" //Password for the corresponding SSID

const char* serverName = "http://api.open-notify.org/iss-now.json"; //The server that I want to gather data from

int APITime = 2500; //"Cooldown" between API calls

String APIValues = ""; //Raw JSON data

String positionData = ""; //Deserialized positional data

void setup() 
{
	Serial.begin(115200); //Begin Serial connection

	WiFi.mode(WIFI_STA); //Use the ESP as a Station and not as an Access point
	WiFi.begin(SSID, PASSWORD); //begin connection with given SSID and PW

	while(WiFi.status() != WL_CONNECTED) { //While not connected to the SSID
		delay(1000); //Wait
	}
}

void loop() 
{
	if(WiFi.status() == WL_CONNECTED){ //if the Wifi is connected
		
		APIValues = httpGETRequest(serverName); //Request Data from the server
		JSONVar obj = JSON.parse(APIValues); //Parse the data as JSON
		positionData = ""; //Reset the position data
		if (JSON.typeof(obj) == "undefined") { //If JSON object is undefined
			return; //stop this cycle
		}
		//Build Position Data String
		positionData = "{";
		positionData += JSON.stringify(obj["iss_position"]["latitude"]); //From reading the API, I gathered the keys to access the data I needed
		positionData += ","; //Comma seperate the two values
		positionData += JSON.stringify(obj["iss_position"]["longitude"]);
		positionData += "}";
		//Send the data
		Serial.println(positionData);
	}

	delay(APITime); //delay between now and next API call
}

String httpGETRequest(const char* serverName) { //Method that returns a String, when given an adress using http GET
	WiFiClient client; //Generate client Object
	HTTPClient http; //generate http object
	
	http.begin(client, serverName); //Give the http object the wifi client and the server adress	
	int httpResponseCode = http.GET(); //send GET request to the server, which will return a response code	
	String payload = "{}"; //Generate a string to hold the data later on
	
	if (httpResponseCode>0) { //If the http response code is not 0
		payload = http.getString(); //get string gathered from GET request
	}
	else {
		payload = ""; //else String is empty
	}

	http.end(); //end connection
	return payload; //return string
}							
						
					

With this step working after so many hours of troubleshooting uploading sketches to the ESP8266 I sadly got a little bit ahead of myself. I quickly wrote the two remaining scripts and connected everything the way I thought it would need to be connected. All of this only to be majorly let down, because of course nothing came through to the end. The biggest problem now was, that I had no tools whatsoever to troubleshoot where the data was being lost, as I could not read the RX/TX from the first Board, as it was used to receive the data form the ESP8266. After some time thinking, I went ahead and tried to get an I2C connection up and running, just between the two Boards in the middle of the plan from above. I wrote two simple scripts using the Wire.h library. I configured one Board as master, sending out messages to a specific adress every three seconds. On the other Hand I had the slave board, where I configured the Adress used in the master sketch via the Wire.begin(adress) command. After a little bit of troubleshooting and stupidity on my side, where I have connected the pins from the master board to the wrong ports on the slave (I reused a Board that was not done for this purpose, so I was confused translating between the SDA/SCL pin numbers and the pin functions I had previously designed for this Board on those pins), I could read out the dots, the master was sending to the slave every three seconds! Knowing that the pure connection was working now, I reassembled everything and hoped for the best. If I was able to read out the ISS' current position on the slave Board, everything was working as intended. The whole structure looked like this in the end:

The complete Assembly

As you can see this is beyond messy and really not ideal. But it is a proof of concept and would be much cleaner with one of these esp8266 programmers. THen the whole Breadboard could be eliminated from the picture. Also a normal power delivery would be nice. I have a breadboard power source at home, but sadly not the correct plug to plug it into the wall.

Apart from this very big mess, I was very tempted to finally take a look at the serial Monitor to look if the data found its way through this mess. At first nothing came up. But I had this problem many times and just restarted the Serial Monitor. And to my great surprise - FINALLY - somethin came up on the serial Monitor. And even somethin legible! Sadly not completely what I wanted however:

The cut off messages I got in the serial Monitor

As you can see, the data is cut off at a specific value for each data packet. While this was quite frustrating in and of itself, I finally got what I had planned before!! Just not compeletly :D

And so I went ahead and searched for the error that was causing this. The error was quickly found. Since each character that is being submitted over I2C takes up one byte, and the standard I2C message is a maximum of 32 Bytes long, only 32 characters could be passed along at a time. Now there were multiple solutions possible for this error:

  • 1) Change the maimum Bufersize within the Wire.h library
  • 2) Split up the message in to two parts
  • 3) Shorten the message to fit within the 32 Byte limit

After thinking about it, I chose to go with the third sollution. I did not chose to change the library, as I did not want to fiddle around with standard libraries too much, as to not break more than i wanted to fix in the first palce. I also did not go with the second options, as there were many characters that were simply not needed at all within the messages. It does not make sense to send the words "latitude" and "longitude" with every message. Since the API and therefore the JSON have a set order of elements, I knew that latitude would always be the first of the two values. This meant, that there was just no need fot these characters, I just needed to send over the raw values. The maximum amount of characters that could be generated through the API calls was now 9 bytes for each numeric value, as well as 7 bytes of spacing and seperation characters. This meant that the maximum number of bytes to send was now 26. The message would then look like this: {"-xxx.xxxx","-yyy.yyyy"}. After reading out the raw values from the JSON, I created my own string, and built it like shown before. With this change, the I2C connection sould no longer be bottlenecking the whole connection line from the ESP to my FTDI to USB Board. After plugging everything back in, I got the following:

The values being transmitted completely!

In the end, the code for both Board looks like follows:

Board 1, my Big Cat Board, using an ATtiny1614:

						
#include <Wire.h> //Needed for I2C
#define I2CAddress 12 //I2C Adress of the second Board (Fully customizable when using 328p as second Board)
#define led 10 //Pin to the onboard LED on the Board for debugging

String text; //text to be sent to the second Board

void setup() {
	Serial.begin(115200); //Begin Serial transmission (115200 because ESP uses it)
	pinMode(led, OUTPUT); //Set LED pin as Output
	Wire.begin(); //Start the I2C connection
}

void loop() {
	text = ""; //Reset text
	while(text.length() == 0) { //If there is no text yet, try to get text
		getText(); //Method to gather data from the ESP
		digitalWrite(led, LOW); //While gathering data, LED off
	}
	if(text.length() > 0) { //If we have gathered data
		digitalWrite(led, HIGH); //turn on LED while sending
		sendText();
	}
}

void getText(){
	if(Serial.available()) // Check if there is data available via Serial
	{
	while(Serial.available()) //While data is Available, do:
	{
		char c = Serial.read(); //read the next available character
		text += c; //Add the character read to the string, to collect the whole string
		delay(10); //delay for a short amount of time
	}
	}
}

void sendText() {
	Wire.beginTransmission(I2CAddress); //transmit to the second Board using its I2C Adress
	for(int i=0; i<(text.length()); i++){ //Go through all characters of the string with a for loop
		Wire.write(text[i]); //Write the individual Scharacters
	}              
	Wire.endTransmission(); //End the transmission if all characters are sent
}		
						
					

Board 2, my Motor Driver Board, using an ATMega328p:

						
#include <Wire.h> //Needed for I2C Connection

#define I2CAddress 12 //Adress of this Node

void setup() {
	Serial.begin(115200);  //Begin Serial Connection
	Wire.begin(I2CAddress); //Begin I2C Connection, set up as device with Dress "I2CAdress" (In this case 12)
	Wire.onReceive(receiveString); //Function that calls another fucntion when data is being received via I2C
}

void loop() {
	delay(10); //Do nothing but delay in loop
}

void receiveString(int bytes) { //Method to recevie a string and send the receied data on via Serial
	String text = ""; //Clear the string
	while (Wire.available()) {  //While I2C has data vaiable for this adress
		char c = Wire.read(); //Read one character at a time
		text += c; //Add the character read to the string
	}
	Serial.println(text); //Print out the complete received String via Serial
}		
						
					

Since now the ISS' current position was being transmitted over from the ESP8266 through Serial over I2C through Serial again, the next task would be to build a program that could read the incomming serial port and use this data to show the ISS' current position on a map. This is what I want to do next week!

As a last Bonus, I wanted to try and send data to another Node with I2C at the same time as I am sending the data to the second Board. For this I used an OLED screen that was I2C compatible. To run the OLED screen, I used a second Breaboard. I connected the 5V and GND pins to the Arduinos 5V and GND Pins and used the SDA and SCL lanes of the Breadboard when the OLED is plugged in as sort of a Bus, which means that Board 1 and Board two were both connected to SDA and SCL on the pins of the OLED. I looked up the I2C Adress of the OLED, which turned out to be 0x3c or 60 in decimal (the adress of my particular model could even be changed by changing the placement of a resistor on the bakside of the panel, in case one wants to run two of these panels). I opened up the sketch for Board 1, and searched for a short tutorial on how to send messages to these OLED panels. After a bit of reading I got to work on the sketch. First of all I added the SSD1306Ascii library via the library manager and included SSD1306Ascii.h as well as SSD1306AsciiWire.h. I defined the OLEDs adress as 60 and set up a SSD1306AsciiWire object. Just like with normal I2C I set call the SSD1306AsciiWire Objects begin method in the setup section. I then set the Font via setFont(). Now I needed to tell the object, to print what I wanted. At first I just printed the whole String, received via the ESP8266. This resulted in the following behaviour:

As you can see, the message was too large to fit into one line, which is why I wanted to change up the string in order to make it fit. For this I copied the String I would gather from the ESP8266 to manipulate it. I manipulate it in a way to gather both numbers, but no other characters, as seperate strings. I then print the whole thing in a way to be able to easily read the coordinates. This resulted in the following outcome:

With this done, I managed to send data to two different devices on the same bus, which made me quite happy. In the end, using the OLED screen, the Code for my first Board, running on an ATtiny1614 looked like this:

						
#include <Wire.h> //Needed for I2C
#include "SSD1306Ascii.h" //Needed for the OLED screen
#include "SSD1306AsciiWire.h" //Needed for the OLED screen
#define I2CAddress 12 //I2C Adress of the second Board (Fully customizable when using 328p as second Board)
#define OLED 60 //I2C Adress of the OLED screen
#define led 10 //Pin to the onboard LED on the Board for debugging

SSD1306AsciiWire oled; //Object to later send data to the OLED through

String text; //text to be sent to the second Board

String oledText; //text to be sent to the OLED screen

void setup() {
	Serial.begin(115200); //Begin Serial transmission (115200 because ESP uses it)
	pinMode(led, OUTPUT); //Set LED pin as Output
	Wire.begin(); //Start the I2C connection
	oled.begin(&Adafruit128x64, OLED); //Start the OLED connection using the screen definiton and the I2C Adress
	oled.setFont(System5x7); //set font of screen
}

void loop() {
	text = ""; //Reset text
	oledText = ""; //Reset OLED text
	while(text.length() == 0) { //If there is no text yet, try to get text
		getText(); //Method to gather data from the ESP
		digitalWrite(led, LOW); //While gathering data, LED off
	}
	if(text.length() > 0) { //If we have gathered data
		digitalWrite(led, HIGH); //turn on LED while sending
		oledText = text; //copy text to oledtext for manipulation
		oled.clear(); //clear the OLED screen with the old data
		String text1 = oledText.substring(0, oledText.indexOf(',')); //data is parted with a comma, so get first string up until comma
		text1 = text1.substring(text1.indexOf('"') + 1, text1.lastIndexOf('"')); //data is between two "; So find first and last Index to get number only
		String text2 = oledText.substring(oledText.indexOf(',') + 1); //same as text1, only that I want everything after the comma
		text2 = text2.substring(text2.indexOf('"') + 1, text2.lastIndexOf('"')); //same as text1
		//Print stuff on the oled screen
		oled.println("Lat: ");
		oled.println(text1);
		oled.println("---------------");
		oled.println("Lon: ");
		oled.println(text2);
		//send text to second Board
		sendText();
	}
}

void getText(){
	if(Serial.available()) // Check if there is data available via Serial
	{
	while(Serial.available()) //While data is Available, do:
	{
		char c = Serial.read(); //read the next available character
		text += c; //Add the character read to the string, to collect the whole string
		delay(10); //delay for a short amount of time
	}
	}
}

void sendText() {
	Wire.beginTransmission(I2CAddress); //transmit to the second Board using its I2C Adress
	for(int i=0; i<(text.length()); i++){ //Go through all characters of the string with a for loop
		Wire.write(text[i]); //Write the individual Scharacters
	}              
	Wire.endTransmission(); //End the transmission if all characters are sent
}			
						
					

In the end I am really happy with this weeks assignment. I managed to do what I wanted and learn many things about the ESP8266 and I2C connections. While this particular setup really does not make much sense, as the first Board could already function as Bridge for the ESP to sent data to a python program, I really wanted to see if I would be able to send data through different means and get something legible in the end, which worked out in the end!