diff --git a/README.md b/README.md index 6b2a1dfa8986d7fee0f6e00ef0d5ff50c7577eda..3302e41daa0da109e9eedb1e35b8faf4e7ea116f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,70 @@ # Morse Code +Worksheet 2: Morse Code + +Part 1: + + +Task 1 was completed which required me to connect to a webserver to access the morse encoder and decoder which would be replicated in python code later in the worksheet. This webserver would be used later in the worksheet in order to verify that the output of my program is correct morse code or alphabetical string. + +Task 2 required me to implement encode and decode functions to encode and decode morse code by taking use of binary trees. + The encode function (morse.py, lines 160-174) works by reading each individual inputted character and searching that letter by trawling through the morse_letters array (morse.py, lines 3-10) and then returning the morse code value of that character. Each part of the morse code is seperated by a space and a space in the string inputted into the encode function is turned into "/". The function takes use of the "find_search" function (tree.py, lines 42-53) to trawl through binary tree and produce the letters morse code reprensentation, this function will also produce an error message if the letter hasn't been implemented. + + The decode function (morse.py, lines 177-195) works in a simpler way to the encode function where it simply takes the dots and dashes and moves left (if a dot) and right (if a dash) through the binary tree that was built (morse.py, lines 14-157). It will then return the character of the morse code and put them together in a string, if a "/" is found then it will implement a space. This will then build the alphabetical string that the morse code represents (i.e. turns "..- .../." into "us e"). The function will return a lower case version of the string by taking use of the ".lower()" function. + +Task 3 required me to implement unit tests for the binary tree and the functions used in the program for the decode and encode functions. This was first done by creating a file "morseunit.py" and importing my binary tree, morse.py and the unittests. + Lines 7-17 test the encode function by using 3 passing tests and 2 tests that intentionally should fail to see if when the unit tests are ran that the correct output is created in the terminal. + + Lines 19-29 test the decode function in the same way but instead of running encode with an alphabetical letter and seeing if it produces the correct morse code output, it does the opposite and takes use of the decode function. 3 tests intentionally pass and 2 tests intentionally should fail (see comments in morseunit.py for which tests are which). + + The insert function (lines 31-39) is tested using 1 test which is meant to pass and another which is meant to fail. The test works by creating a binary tree (e.g. line 33) and then using the "insert_search" function (tree.py, lines 29-41) to insert an alphabetical letter and its morse code representation beneath the first inputted character in the binary tree. Then "self.assertEqual" is used to test that the charcter has been indeed inputted and that it is in the correct place, an error message is produced if these conditions are not met. + + The find function (lines 53-65) is tested using 1 test which is meant to pass and another which is meant to fail. First a empty binary tree and morse code letter array are made, then a for function is used to input the letter in the array to their correct positions and build up the binary tree. Finally, the find function is used to then see if the alphabetical letter has the matching morse code representation (for the test that fails the morse code should not match the letter which produces an error message). this will tell us that the find function works as intended and that the binary tree has successfully been made and inputted the characters and morse in the correct positions. + + To test that a binary tree is correctly empty (lines 41-44) a binary tree is first created (line 43) with no child nodes. Then, the function "treeEmpty" is used (tree.py, lines 56-61) which takes use of if functions to check both left and right child nodes and returns true if they are empty and false if they have values. The unittest passes as the binary tree created is empty and the output is true, therefore all conditions are met. + + To test that a binary tree is not empty (lines 46-51) the same thing that was done for if a tree is empty is used but the function "treeNotEmpty" is used (tree.py, lines 63-68). This function works in the same way but if child nodes are detected then true is returned and if they aren't then false is returned. A binary tree is created (line 48) and then a child node is added (line 49) and then the "self.assertEqual" function is used to see that the correct conditions are met and that "true" is returned. + +Task 4 required the implementation of special characters into the existing binary tree and that unit tests were performed to make sure that implementation of these characters was carried out correctly. + In lines 54 to 73 (morse.py) each special character and its morse code counterpart were defined. Lines 93 to 157 then show that these characters were implemented into their respective places in the binary tree. However, new "blank" variables were created (morse.py, lines 76-89) so that the special characters were put in their correct places as there were no existing values for these parts of the binary tree. + + In morseunit.py all of these special characters are tested (lines 70-121) by taking use of the encodeand decode functions and seeing if when encoded into morse code (or vice versa) that they produce their correct morse code representations/character strings. This tells us that they have been correctly implemented into the binary tree and that they are in their respective positions. All of these tests are designed to pass. + + Finally, when the morseunit.py file is ran we can see that 33 tests have been ran that that 6 failed. The output in the console tells us that these are indeed the correct ones which are designed to fail. This also tells us that every test and implentation made in the program is correct from all of the parts of part 1 of worksheet 2. + + +Part 2: + + +Task 1 required me to create a decode function which takes use of a binary heap rather than a binary tree. I was also tasked to discuss the differences between decoding messages using binary heaps and binary trees + The decode using binary heaps was completed in the morse.py, lines 207-230. Binary heaps work off arrays, so one was created which incorporated all characters, numbers, special characters and blanks (lines 209-214). For and if functions were then implemented to extract each morse code letter inputted to be decoded, which would then use the dots and/or dashes to naviagte through the letters array using the "(2*m)" (for dots) and "(2*m)+1" (for dashes). These are present in lines 219-227, slashes are also interpretted as spaces between the decoded words. Line 229 then compiles the decoded message and returns the string in lower case. In morseunit.py (lines 123-131) 4 unit tests were conducted to test the functions functionality (2 should pass and 2 should fail) and it doesthis by simply calling the function and comparing the functions output to the desired answer (or undesired for the tests that are designed to fail), we know that the function works as intended if the two that fail produce their error message into console and the other 2 don't. + + A difference between decoding using binary heaps and decoding using binary trees is that binary heaps are a complete binary tree that use arrays to navigate through the trees nodes. To naviage through the array (for this assignment for example) the formula "(2*m)" and "(2*m)+1" are used, whereas for binary trees we have to setup each node and it's morse code representation and manually assign it's position in the binary tree (this is because it is an ordered data structure), but binary heaps this is all done by simply creating the array. Another difference is that binary tree's allow for duplicates but heaps don't, so seeing that the order of the nodes matter for our decode function then it is better to use binary tree's for decoding more messages. Another difference is that if a very large amount of nodes needs to be created (i.e. incorporating other symbols for other languages) then a binary heap would be more suitable for the decode function as they are created in a linear fashion whereas a binary tree needs to be created manually. + + A way in which the "decode_bt" function could be replicated using python dictionaries is by first creating the dictionary where you assign each key (or character in our case) with it's value (its morse code representation). Using python dictionaries allows you to translate the key (character) into a callable variable (as a reference) so when a variable is used it provides the location of it in the dictionary. Therefore rather than creating a binary tree and specifying each location we can simply order each node in the dictionary and then use it's reference to provide the location of it (for the dots and dashes) + +Task 2 required me to implement "encode_ham" and "decode_ham" functions which are used for encoding and decoding mrose code messages that carry the values of the sender and reciver as well as the message being transmitted. + The "encode_ham" function first formats the inputted values into the correct format for transmitting ham messages ("receiver"de"sender"="message"=(). Then it simply calls the previously defined "encode" function to encode the formatted message into morse code ready to be transmitted. + + The "decode_ham" function uses the recieved morse code message and calls the previously defined "decode" function to turn the morse code message into a string of characters. Next, it then splits (using .split function) each part of the message to extract the sender, receiver and the message into their own variables. Finally, the decoded message is then compiled and formatted as "Recevier: "receiver", Sender: "sender", Message: "message"" which is then returned. + + The unit tests used to test the "encode_ham" function (morseunit.py, lines 133-137) simply calls the function inputting the sender receiver and message and then compares the output of the function to the desired output. 1 test is designed to pass and another to fail. The test that passes matches up and doesn't produce a failure, whereas the sender was changed for the fialed test so it doesn't match up with the desired output therefore creating a failure. This shows us that the formatting of the encode is correct and that it correctly puts the receiver, sender and messsage in its respective places. The "decode_ham" function was tested in the same way with 1 test passing and 1 test failing (morseunit.py, lines 139-143) + +Task 3 required to develop "send_echo" and "send_time" functions that implemented sending messages to the extended morse server by using websockets. These were all done in the morse.py file and their unit tests in morseunit.py + "send_echo" works by first loading up the websocket with the specified url ("ws://localhost:10102") then if the websocket has the correct join message that showing we have access to the websocket then the rest of the function is carried out, however if the join message doesn't match up with our desired one then an error message is produced and 0 is returned to console. If the join message is correct then the "encode_ham" message is called to compile the sender and message with "echo" as the reciever to produce the required echo output with the morse server. This encoded morse message is then transmitted and the function will wait for a response from the server which is then decoded using "decode_ham" and printed to console. + + To be able to transmit the message to the webserver another function had to be created called "sendMsg" which takes the encoded message (line 257) and then contacts the webserver using the specified websocket and applies my client ID with the message as the payload for the message. This is done so that the webserver knows which client is attempting to contact the server and whether the correct permissions allow me to transmit a payload and execute the desired code. + + Also, to be able to receive the response message back from the webserver "receiveMsg" async function was made (lines 274-276) which simply waits for the webserver to send the message to the user and then returns the messages payload (which will be the morse code response). This is implemented in line 260 for the "send_echo" function, where it collects the response message where it is then decoded in the next line. + + "send_time" works in the exact same way as "send_echo" however rather than the recipient of the encoded message being "echo" it is changed to "time" and the message payload is empty as all we need is the response and no message is required to be sent to the time part of the webserver. This again takes use of both "sendMsg" and "recieveMsg". + + Unittests were conducted for both the "send_echo" and "send_time" functions, unittests aren't needed for "sendMsg" or "recieveMsg" as if the unittests for "send_echo" and "send_time" work out correctly that tells us that they both work properly as both echo and time take use of them. Lines 146-147 (morseunit.py) is a passing test for "send_echo" which simply executes the function and checks to see if it echo's the message sent back to us. Lines 149-156 test the "send_time" function which imports "time" package where we then take us of this to capture the current time of when the function is run (Line 153) and then this is formatted to our desired formatting (line 154) where it is then stored in a variable. The function is then tested in the same way as the unnitest for "send_echo" but the message received isn't hard set and uses the time variable so that it uses the current time of when the function is ran rather than a time that doesn't change. If these unnitests do not pass then a unique error message is displayed for both in the terminal. + + + + + + + +- \ No newline at end of file diff --git a/assert_tests.py b/assert_tests.py index 9b143d18e47a39920812fcde48d7baa63decba96..553976d4d631eba5a3ad5a95118e07b542c268fb 100644 --- a/assert_tests.py +++ b/assert_tests.py @@ -1,17 +1,16 @@ import morse import tree +#assert test for encode def test_encode_us(): assert morse.encode("us") == "..- ...", "Should be ..- ..." +#assert test for decode def test_decode_us(): assert morse.decode("..- ...") == "us", "Should be us" -def test_binary_insert(): - #assert morse.encode(" ") - if __name__ == "__main__": test_encode_us() test_decode_us() - print("Everything passed") + print("Everything passed") #success message produced if both testing functions pass diff --git a/main.py b/main.py index 5ba15dc497dd369f642733a9f0961a84369d2b46..db2a9f646eafccdd1942187cdf09b798ea7af792 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,7 @@ import morse import assert_tests +#Assert tests that tes the functionality of the encode and decode functions that are defined in morse.py and ensure they work as intended if __name__ == "__main__": assert_tests.test_encode_us() print("Everything passed") diff --git a/morse.py b/morse.py index dc92760e6f19945db79e3d56a3b605ad1bb1b52f..2090903b8188a3ac6e555d8db5ff374493bdb837 100644 --- a/morse.py +++ b/morse.py @@ -229,6 +229,7 @@ def decode_bt(decode_msg): decodedMorse = decodedMorse + letters[m-1] return str(decodedMorse.lower()) +#Function that encodes characrets into morse code using the correct formatting for Ham radio conversations def encode_ham(sender, receiver, msg): encodedHam = receiver + "de" + sender + "=" + msg + "=(" @@ -236,15 +237,18 @@ def encode_ham(sender, receiver, msg): return encodedMsg +#Decode function used to decode the recived Ham radio messages so that they can be read and understood, formatted correctly to show sender and receiver as well as the message def decode_ham(decodeMsg): decodeHam = decode(decodeMsg) receiver = decodeHam.split("de") sender = receiver[1].split("=") - msg = "Receiver: " + receiver[0] + ", Sender: " + sender[0] + ", Message: " + sender[1] + message = sender[1] + msg = "Receiver: " + receiver[0] + ", Sender: " + sender[0] + ", Message: " + message[0] return msg +#async function that contacts the extended morse server to initiate the echo command and displays the echo back into console async def send_echo(sender, msg): url = "ws://localhost:10102" @@ -265,15 +269,18 @@ async def send_echo(sender, msg): print("Correct join message not received") return 0 +#Function used for sending off messages to the webserver async def sendMsg(websocket, msg, client_id): sentMsg = {"type" : "morse_evt", "client_id" : client_id, "payload" : msg} await websocket.send(json.dumps(sentMsg)) +#Function used for recieving the response from the webserver async def receiveMsg(websocket): msg = json.loads(await websocket.recv()) return msg["payload"] +#Async function that contacts the webserver to get the current time of when this is ran async def send_time(sender): url = "ws://localhost:10102" @@ -293,8 +300,3 @@ async def send_time(sender): else: print("Correct join message not received") return 0 - -#asyncio.run(send_echo("Joe", "hello")) -asyncio.run(send_time("Joe")) -#print(encode_ham("Joe", "Gromit", "wenslydale")) -#print(decode_ham("--. .-. --- -- .. - -.. . .-- .- .-.. .-.. .- -.-. . -...- .-- . -. ... .-.. -.-- -.. .- .-.. . -...- -.--.")) diff --git a/tree.py b/tree.py index 06d5c119ebcc81a09c5c6adcb3f44e1b226daeda..71879e9013ba1ad253cbf3db531f4af25ff3f174 100644 --- a/tree.py +++ b/tree.py @@ -17,6 +17,7 @@ class binary_tree(): def get_node(self): return self.rootid + #Function that lets us print the current binary tree to console def print_tree(self, nest): if self != None: for i in range(nest):