diff --git a/Makefile b/Makefile index 8274b3f..b00080f 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ ifeq ($(firstword $(MAKECMDGOALS)),format) endif endif -.PHONY: help release publish-manifest format +.PHONY: help release publish-manifest format docs help: @printf '%s\n' \ @@ -27,7 +27,8 @@ help: ' make release Run the Firmware Release Manager.' \ ' make publish-manifest Update ota/manifest.json from release-info.json.' \ ' make format all Format all .ino/.c/.cpp/.h files under src/.' \ - ' make format Format one file under src/ (basename or src/).' + ' make format Format one file under src/ (basename or src/).' \ + ' make docs Build the technical documentation PDF.' release: @$(MAKE) -C "$(FRM_DIR)" ROOT_DIR="$(CURDIR)" release @@ -62,3 +63,10 @@ format: fi; \ printf '[format] Formatting %s file(s) with clang-format\n' "$${#files[@]}"; \ clang-format -i -style=file -- "$${files[@]}" + +docs: + @set -euo pipefail; \ + command -v asciidoctor-pdf >/dev/null || { echo 'ERROR: asciidoctor-pdf is required.'; echo 'Install with: gem install asciidoctor-pdf'; exit 1; }; \ + mkdir -p build; \ + asciidoctor-pdf docs/tech_documentation/book.adoc -o build/Technical_Documentation.pdf; \ + echo "[docs] Wrote build/Technical_Documentation.pdf" diff --git a/README.md b/README.md index f3e41dc..f78c179 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ ![Repository Banner](docs/graphic_materials/github_repo_animation.gif) # 42 Smart Cluster Sign + + ## Table of Contents - [Introduction](#introduction) - [Usage](#usage) - [Features](#features) - [Components](#components) -- [The Team behind the Sign](#the-team-behind-the-sign) +- [Team behind the Sign](#team-behind-the-sign) - [Regards](#regards) - [Contributing to the Project](#contributing-to-the-project) - [License](#license) @@ -17,7 +19,7 @@ Welcome to the README for the 42 Smart Cluster Sign — a self-sufficient information display designed to be installed on a cluster door. Its purpose is to notify students when the cluster is reserved for an exam and prevent them from accidental entering. The device automatically retrieves exam dates from Intra and displays appropriate warnings and information on its e-paper screen. In the spare time it simply displays the cluster number. -For more information, please, refer to the technical documentation in the docs folder of this repository. +For more information, such as maintenance and development, device build guide, architecture and function reference, please refer to the [Technical Documentation](docs/tech_documentation/README.md). ## Usage @@ -54,19 +56,10 @@ The following components are used in the 42 Smart Cluster Sign: 5. **IKEA RÖDALM photo frame, black, 13x18 cm**: made a good enclosure. 6. **Custom 3D-printed board**: to hold all the electronics in place. -For more information, please, refer to the bill of materials in the docs folder of this repository. - - -## Contributing to the Project - -Contributions to the 42 Smart Cluster Sign project are very welcome! Contact the repository admin [HERE](https://www.linkedin.com/in/roman-alexandrov-a75b89195/) to be added as a Collaborator*. The best place to start would be the [**Issues**](https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign/issues) tab of this repository. It most likely already contains a list of features we'd appreciate your help with and you can start working on them right away. If you have your own ideas, bug fixes, or improvements, feel free to open an issue or submit a pull request. - -When contributing, please adhere to the existing code style and follow the established guidelines. Clearly describe your changes and provide any necessary documentation or tests. - -*Since the device is intended for use within the 42 network of schools, its development requires personal access to the internal information system. For this reason, only a student or a member of the Bocal staff at a 42 school can become a Collaborator on this project. +For more information, please, refer to the [Bill of Materials](docs/Bill_of_Materials.xlsx) in the docs folder of this repository (Excel file, not viewable on GitHub -- download to view). -## The Team behind the Sign +## Team behind the Sign This project is a group effort of various 42 students with support from the 42 Prague Bocal team. Here they are: - **roaleksa**, 42 Roma, [linkedin](https://www.linkedin.com/in/roman-alexandrov-a75b89195/) — software and electronic hardware developer. Made the idea reality, @@ -80,11 +73,19 @@ This project is a group effort of various 42 students with support from the 42 P ## Regards -The project is based on Jean-Marc Zingg's [GxEPD2](https://github.com/ZinggJM/GxEPD2) library for e-paper displays. -The project uses the [ArduinoOTA](https://github.com/jandrassy/ArduinoOTA) library by Juraj Andrassy for the Over-The-Air software update functionality. +The project uses Jean-Marc Zingg's [GxEPD2](https://github.com/ZinggJM/GxEPD2) advanced library of drivers for e-paper displays. The Sign's Telegram Bot functionality is provided by Brian Lough's [UniversalTelegramBot](https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot) library. +## Contributing to the Project + +Contributions to the 42 Smart Cluster Sign project are very welcome! Contact the repository admin [HERE](https://www.linkedin.com/in/roman-alexandrov-a75b89195/) to be added as a Collaborator*. The best place to start would be the [**Issues**](https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign/issues) tab of this repository. It most likely already contains a list of features we'd appreciate your help with and you can start working on them right away. If you have your own ideas, bug fixes, or improvements, feel free to open an issue or submit a pull request. + +When contributing, please adhere to the existing code style and follow the established guidelines. Clearly describe your changes and provide any necessary documentation or tests. + +*Since the device is intended for use within the 42 network of schools, its development requires personal access to the internal information system. For this reason, only a student or a member of the Bocal staff at a 42 school can become a Collaborator on this project. + + ## License The 42 Smart Cluster Sign project is licensed. Please, familiarise yourself with the license before using the software or working on it. The text of the license can be found in this repository. @@ -94,4 +95,4 @@ Please note that while the project strives to provide accurate information, it i ## Conclusion -Thank you for your interest in the 42 Smart Cluster Sign project! We hope this README provides you with the necessary information to understand the project's purpose, features, installation process, usage, and maintenance. If you did not find the information you need, please, refer to the technical documentation in this repository. If you have any further questions or need assistance, please don't hesitate to reach out. Happy coding! \ No newline at end of file +Thank you for your interest in the 42 Smart Cluster Sign project! We hope this README provides you with the necessary information to understand the project's purpose, features, installation process, usage and maintenance. If you did not find the information you need, please refer to the [Technical Documentation](docs/tech_documentation/README.md). If you have any further questions or need assistance, please don't hesitate to reach out to the author on [LinkedIn](https://www.linkedin.com/in/roman-alexandrov-a75b89195/). Happy coding! diff --git a/docs/Technical Documentation.docx b/docs/Technical Documentation.docx deleted file mode 100644 index 11a2d02..0000000 Binary files a/docs/Technical Documentation.docx and /dev/null differ diff --git a/docs/tech_documentation/01-vocabulary-of-terms.adoc b/docs/tech_documentation/01-vocabulary-of-terms.adoc new file mode 100644 index 0000000..d56f677 --- /dev/null +++ b/docs/tech_documentation/01-vocabulary-of-terms.adoc @@ -0,0 +1,32 @@ += Vocabulary of Terms +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +*42 Smart Cluster Sign* — in the project files may be refered to as „the Sign“, „the device“. + +*Campus* — physical premises of a 42 school, mainly consist of clusters. + +*Cluster* — an auditorium, a study room, usually full of workspaces with computers. For example, C3 is short for Cluster 3. + +*Intra* — a website of the 42 school internal information system, but in this project „Intra“ rather means the school‘s internal information system server, as the Sign never accesses the website as a common user, but only the server via its API. + +*Secret* — a credential. Along with a UID, it allows to log into Intra via its API. Usually, good for a month, then it expires. Not to be confused with „a security token“ or just „a token“. + +*Security token* — also refered to as „token“, is a unique short-lifespan key given by Intra that allows the device to access data stored on the server. Usually, good for a few hours. + +*Seeed Studio XIAO ESP32-C3* — a development board with an ESP32-C3 microcontroller and a battery charging IC under the shield. Has two buttons: „B“ stands for „BOOT“, and „R“ stands for „RESET/REBOOT“. Has a red LED indicating the states of the battery charging process. + +*ESP32-C3* — the microcontroller onboard the Seeed Studio XIAO ESP32-C3 development board, located under the shield. + +*Deep Sleep* — a functionality of an ESP32-C3 microcontroller allowing it to stay ON while consuming almost no power. The most effective way to save battery charge, but reboots the microcontroller causing all the temporary data in RAM to be lost. + +*RTC memory* — a small section of the ESP32-C3 memory that stays powered over Deep Sleep. Data put into this section will survive Deep Sleep, but will not survive resetting the device with the reset button. In this project, only data put into the file system can survive both Deep Sleep and resetting the device with the reset button. + +*Light Sleep* — a functionality of an ESP32-C3 microcontroller similar to Deep Sleep. Light Sleep is far less effective in saving battery charge, but does not lose RAM data and allowes to contunue executing the program after sleep. + +*OTA* — stands for „Over-The-Air“, functionality that allows updating the software of the microcontroller wirelessly. + +*SPIFFS* — stands for Serial Peripheral Interface Flash File System, is one of the ESP32-C3 microcontroller memory partitions dedicated to storing files. In this project (and often on the Internet) the term is used as a synonym to „a file system“. The term also serves as a name to the formerely widly-spread SPIFFS library. Instead of it, this project employs the LittleFS library as more modern and light-weight. diff --git a/docs/tech_documentation/02-about-the-project.adoc b/docs/tech_documentation/02-about-the-project.adoc new file mode 100644 index 0000000..26377f9 --- /dev/null +++ b/docs/tech_documentation/02-about-the-project.adoc @@ -0,0 +1,8 @@ += About the Project +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +This project is designed to manage and display information on a smart sign for a 42 campus cluster. The sign displays various messages and images, including exam schedules, battery status, and OTA updates. It communicates with the 42 Intra API and a Telegram bot to fetch and display relevant information. diff --git a/docs/tech_documentation/03-contractors-requirements.adoc b/docs/tech_documentation/03-contractors-requirements.adoc new file mode 100644 index 0000000..c45b045 --- /dev/null +++ b/docs/tech_documentation/03-contractors-requirements.adoc @@ -0,0 +1,40 @@ += Contractor's Requirements and How the Sign Matches Them +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +The finished device must fulfill the following requirements: + +. *Auditorium status display*: the device must always display the current status of the auditorium, indicating whether it is free or if an exam is in progress. ++ +The Sign displays the number of the auditorium in calm black and white colours to indicate that the room is available for everyone or it displays the „exam in progress“ sign in bright red and black colours to show that the room shall not be entered by those who do not participate in the exam. The Sign is never blank. + +. *Advance notification*: the device should notify students in advance on exam days about the need to vacate the auditorium. ++ +The Sign displays a note with the exact time of the upcoming exam right from the morning of that day. 1 hour before the exam, the black-white-and-red „reserve for exam“ sign with time left starts being displayed. + +. *Autonomy*: the device must operate independently, performing its tasks and solving problems that arise without requiring the time or intervention of the educational institution's staff. ++ +The Sign can connect to the Internet, access the institution server API and pull exams date and time. This is how it knows when to display the apropriate state of the auditorium. The Sign software is designed with all the common negative situations in mind which ensures the Sign does not bother anyone unless it absolutely needs to. + +. *Problem reporting*: the device must be capable of reporting issues that cannot be resolved without assistance from the educational institution's staff. ++ +When the Sign fails to resolve an unordinary situation itself and requires assistance, it displays an apropriate error message on its display as well as sends a detailed error report to its Telegram chat. + +. *Support and expandability*: the device should be designed for support and expansion, allowing any student of the educational institution to contribute to the project, develop new functionality, and upload new software onto the device. ++ +The Sign project was chosen to be made using Arduino IDE as the most beginners-friendly developing platform. The software was developed using a straightforward bare-metal approach to maintain easy-to-follow program logic. The Sign has a standart USB-C port for flashing its software, monitoring its Serial port and charging its battery. The microcontroller used in the project has inner USB controller which eliminates the need of using a UART-TTL adapter. The microcontroller pins are equipped with standart Dupont sockets which allows anyone to change used pins and add new hardware by simply connecting it to the microcontroller with Dupont cables. The Sign is securelly fixed on the wall with 4 furniture double ball catches, which at the same time allow to take the Sign off the wall for maintanance. + +. *Safety*: the device must ensure safety by incorporating protective elements for potentially dangerous electronic components. ++ +The battery used in the project has an embedded protection against shortcuts, overcharging and ungercharging, which has the ability of completely disconnecting the battery from the rest of the circuit. The battery charging IC is capable of adapting its charging power, applying lower voltages when the battery is low on charge or close to being fully charged. The charging IC generally uses slightly lower charging rate than the standart charging rate for this particular battery, which will result in the battery longer life. + +. *Rechargeable operation*: the device must operate on a rechargeable battery, include a common charging connector, and provide indications for the charging process and its completion. ++ +The Sign employs an internal high-capacity rechargeable battery, which insures a long lasting operation without the need of changing batteries. The battery can be recharged by plugging the Sign into any 5V power adapter. The Sign has a USB-C connector for recharging the battery as well as for flashing the software. The Sign employs a red LED to indicate an ongoing charging process. + +. *Design compatibility*: both the graphical user interface (GUI) and the physical appearance of the device must align with the established style of the educational institution's interior design. ++ +The body of the Sign is made of black wooden frame with mate finish, which perfectly matches the mate-black profile of the auditorium glass door. The GUI was designed inspired by the painings on the walls of the institution as well as the „42“ logo, thus nicely matching the overal style of the interiors. diff --git a/docs/tech_documentation/04-program-run-overview.adoc b/docs/tech_documentation/04-program-run-overview.adoc new file mode 100644 index 0000000..ec091f9 --- /dev/null +++ b/docs/tech_documentation/04-program-run-overview.adoc @@ -0,0 +1,21 @@ += General Description of the Program Run +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +Let's take a look at one day of the 42 Smart Cluster Sign‘s life. Let’s say that on this particular day there is an exam scheduled for 13:00 and it will last for 3 hours. + +It is still night and the Sign is showing the cluster number with the default pictograms from the day before, while still being asleep. It is normal for the Sign to sleep all the time. It has its scheduled hours to wake up and check if something needs to be done. They are 6, 9, 12, 15, 18 and 21 o’clock (could have been changed in the program). But it is still too early. + +Time passes by. Now, it is 6:00 in the morning. The Sign wakes up to check if there are exams today. It goes online, pulls the information from the Intra server and sees that there will be an exam starting at 13:00 and ending at 16:00. The Sign replaces the default pictograms from yesterday with a note “_The cluster will be reserved for an exam today at 13:00_” while still displaying the cluster number. Since there is nothing more for the Sign to do, it sets its alarm clock for 12:00 - an hour before the exam - and goes back to sleep. + +It is 12 o'clock and the Sign wakes up again to get ready for the exam. It checks Intra to make sure the exam was not canceled during its sleep and that there is at least 1 person attending. If everything checks, it replaces the cluster number with a big warning sign that says: “_RESERVATION! The cluster is reserved for an exam. Please, vacate it in due time. You have XX minutes left_”. Instead of XX it first says 50 minutes, then 25 minutes and finally 5 minutes left. + +Finally, it is 13:00. The exam begins. The Sign changes the previous warning sign for a new one, saying “_DO NOT ENTER! Exam in progress!_”. At this point the Sign has nothing else to do, so again it sets its alarm clock for 16:00 - the end of the exam - and goes to sleep. + +At 16 o'clock the Sign wakes up, checks Intra, finds no more exams for today, replaces the warning sign with a cluster number with the default pictograms, sets its alarm clock until the next scheduled wake up - in this case 18:00 - and goes back to sleep. + +At 18 o'clock the Sign wakes up, checks Intra and finds no more exams, so it does nothing. Later, at 21 o’clock it wakes up for the last time today, finding nothing more to be done. Its work for today is over. It goes to sleep to wake up again the next morning at 6. + diff --git a/docs/tech_documentation/05-program-run-step-by-step.adoc b/docs/tech_documentation/05-program-run-step-by-step.adoc new file mode 100644 index 0000000..84f338c --- /dev/null +++ b/docs/tech_documentation/05-program-run-step-by-step.adoc @@ -0,0 +1,96 @@ += Program Run Step-by-Step +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +== Starting the Program + +1. Turning on + +2. Initializing the Serial Port + +• Outputting a dash to the serial port gives time to synchronize data transfer with the computer and avoid losing important data + +3. Initializing the file system + +• If the file system initialization fails, the following functions will not be available: restoring the last used Telegram chat, the Secret value, and the OTA flag value after reinstalling the program, after a power outage, or after a software reset (i.e. after all cases when RTC memory data is lost); record the value of the last used Telegram chat, the Secret value and the OTA flag value + +4. initialize the buttons (disabled due to bugs) + +5. initialize ADC for battery measurements + +6. initialize the SPI port of the display + +7. check the reboot reason + +• also restores the value of the last used Telegram chat, the Secret value and the OTA flag value after reinstalling the program, after a power outage or after a software reset (i.e. after all cases when RTC memory data is lost) + +• also puts the device to sleep for 24 hours if the BROWN OUT detector is triggered. It is triggered if the battery charge is insufficient to continue operation. In this case, [DEVICE OPERATION ENDS HERE] until the battery is charged. + +8. battery check + +• due to the technical features of the device, we can determine the battery charge level only when it is almost discharged. Accurate battery measurements can be taken approximately between 3% and 0% of the battery charge. In ADC readings, this corresponds to 800 and 400. + +• take 5 measurements of the charge level and calculate their average value + +• all readings above 800 mean that the battery is sufficiently charged and there is no need to report a low battery - exit the function + +• connect to Wi-Fi to report the battery status to Telegram + +• all readings below 400 mean a completely discharged battery. Despite the fact that the BROWN OUT detector did not work in the previous step, you cannot continue working with such a low charge. We report a low battery in Telegram, display the message "Low battery" on the display and put the device to sleep for 24 hours. In this case, [DEVICE OPERATION ENDS HERE] until the battery is charged. + +• indicators between 700 and 600 may mean that the device is charging + +• if we still haven't exited the function, but the indicators are below 800, then the battery is already discharged, but we can still continue working. We report the discharged battery in Telegram, display the message "Low battery" on the display and continue executing the program. + +9. initializing the OTA function (disabled due to blocking by the firewall) + +10. switching to OTA mode (disabled due to blocking by the firewall) + +11. choosing which mode to continue working in: in cluster number mode (default mode) or in exam mode + +• Cluster number mode displays the cluster number + icons (on a normal day) or a warning message about the exam (on the day of the exam) or error messages (inability to receive exam data, expired Secret, low battery). This mode is active 99% of the time. + +• The exam mode is activated 1 hour before the exam, shows a warning about the exam starting soon, then switches to a warning about the exam in progress and after the exam is over, switches back to the Cluster Number Mode. This mode is active only on the exam day, 1 hour before the exam + the entire exam time. + +WARNING: Double checking the exam status flag in this function is necessary to switch from one mode to another. Do not change! + +== In Cluster Number Mode + +1. connecting to a Wi-Fi network + +2. checking incoming messages in the Telegram chat + +• A new Secret may arrive via Telegram chat, which will be useful later when requesting exam data + +3. synchronizing time, date and summer/winter time with the NTP server + +• Without time data, reliable operation of the device cannot be ensured. If after several attempts to receive time data it was not possible, an error is displayed on the display and the device itself is put to sleep until the next scheduled awakening. In this case, [DEVICE OPERATION ENDS HERE], until we manage to get the time data during one of the future scheduled awakenings. + +4. get exam data for the current day from Intra + +• Without exam data, it is impossible to ensure reliable operation of the device. If after several attempts to get exam data it was not possible, an error is displayed on the display and the device itself is put to sleep until the next scheduled awakening. In this case, [DEVICE OPERATION ENDS HERE], until we manage to get exam data during one of the future scheduled awakenings. + +• go to the Intra website + +• log in to the Intra website + +• go to the schedule page for today + +• read the received HTML code until we find exam data + +• clear the data from unnecessary garbage + +• compare the received data with the existing data + +If the data differs, we change it on the display; + +If not, we leave it as it is. + +Setting the time for the next activation. + +Turning off the display power. + +Power off. diff --git a/docs/tech_documentation/06-how-to-build-the-sign.adoc b/docs/tech_documentation/06-how-to-build-the-sign.adoc new file mode 100644 index 0000000..aa72203 --- /dev/null +++ b/docs/tech_documentation/06-how-to-build-the-sign.adoc @@ -0,0 +1,110 @@ += How to Build the Sign Yourself +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +YOU WANT TO MAKE YOUR OWN SIGN FOR A 42 CAMPUS BUT THIS INSTRUCTION IS NOT COMPLETE YET? + +CONTACT THE AUTHOR FOR PERSONAL FREE-OF-CHARGE ONLINE CONSULTATIONS! + +LINKEDIN: link:https://www.linkedin.com/in/roman-alexandrov-a75b89195/[https://www.linkedin.com/in/roman-alexandrov-a75b89195/] + +Building the 42 Smart Cluster Sign involves assembling the hardware components, setting up the software, and configuring the device. Follow these instructions to build your own 42 Smart Cluster Sign. + +== Hardware Components + +The detailed information about the hardware components can be found in the Bill_of_Materials.xlsx file, located in the docs folder of the project, but here is a simple list: + +. Seeed Studio XIAO ESP32C3 Wi-Fi module +. External WiFi antenna 2.4G with IPEX1 connector +. Good Display GDEY075Z08 7.5" 800x480 ePaper black/red/white SPI display +. Good Display DESPI-C02 universal SPI e-Paper adapter +. Dupont male-to-female wires for internal wiring +. Push buttons +. 4000mAh Li-ion battery with overcharge and undercharge protection +. custom 3D-printed board +. IKEA RÖDALM photo frame, 13x18 cm +. furniture ball catches +. transparent plexiglass pannel, 2.5mm or thiker, 13x18 cm +. transparent double-sided sticky tape + +== Tools Required + +* soldering iron and solder +* screwdrivers +* 3D printer +* computer with any OS +* USB to USB-C data cable compatible with your computer +* 5V power adapter + +== Needed software + +* Arduino IDE with installed ESP-IDF plug-in, +* Telegram (smartphone app or its desktop version). + +== Display Pins + +[cols="1,1,1",options="header"] +|=== +|XIAO ESP32C3 Pins +|Display adapter Pins +|Pin Description + +|D4 +|*BUSY* +|busy / free status line + +|D5 +|*RES* +|reset line + +|D6 +|*D/C* +|Data mode / Command mode select + +|D7 +|*CS* (also called *SS*) +|Chip Select + +|D8 +|*SCK* +|common clock + +|D10 +|*SDI* +|data line + +|GND +|*GND* +|ground line + +|3.3V +|*3.3V* +|power line +|=== + +== Building Steps + +. Order the hardware components +. Print the custom 3D-printed board +. Create an Intra API app for your Sign +. Create a Telegram bot for your Sign +. Install Arduino IDE and flash XIAO ESP32C3 with the program +. {empty} + +== Alternatives For The Hardware Components + +Compatibility with any alternatives to the hardware stated in the hardware list above was not tested. Using alternative hardware components may complicate building the Sign in an unexpected way. Still, here are some advices if you decide to seek alternative hardware: + +* if you want to replace the Seeed Studio XIAO ESP32C3 Wi-Fi module with any other Seeed Studio ESP32 module, make sure that it has at least the same ammount of RAM, since the project software is RAM intensive. Replacing it with other ESP32 modules may also require to adjust the custom 3D-printed board; +* you may use any Wi-Fi antenna as long as it fits the module; +* the project software uses Jean-Marc Zingg's GxEPD2 library for e-paper displays. The library has its list of supported e-paper displays and your alternative display should be on the list. Using any alternative dislay will always require adjusting the project software. Using an alternative display with different resolution will also require to remake all the GUI images used in the project. Using an alternative display with different physical size and/or different physical proportions will also require to adjust the custom 3D-printed board. Using an alternative display adapter would probably require to adjust the custom 3D-printed board, too; +* if you do not need modularity in your version of the Sign, it would be easier to solder ordinary wires instead of using Dupont wires. If needed, Dupont male connectors may be soldered, too; +* you may use any push-buttons as long as you are willing to adjust the custom 3D-printed board to fit them; +* you may use any battery as long as it is rated 3.7V and is capable of constantly outputting at least 250mA. Smaller alternative batteries may be simply fixed on the 3D-printed board with a double-sided sticky tape. Bigger alternative batteries may require to adjust the custom 3D-printed board to fit them; +* you may avoid printing the custom 3D-printed board and simply glue all the electronic components inside of the frame. In that case, be adviced that e-paper displays do not like ANY heat and an ESP32 can get very warm during work and even hotter when the battery is being charged, so make sure to have some good insulation between the two; +* any frame would work as long as it is deep enough to accomodate all the electronics. It does not even has to be a frame, but any enclosure you can produce; +* as the furniture ball catches can be quite pricy, you may want to 3D-print the catches yourself. Luckily, there is plenty of ready-to-print models on the Internet. +* if you do not need your Sign to be detachable from the wall, you will not need the furniture ball catches and the transparent plexiglass pannel. diff --git a/docs/tech_documentation/07-development-environment.adoc b/docs/tech_documentation/07-development-environment.adoc new file mode 100644 index 0000000..2f7f34b --- /dev/null +++ b/docs/tech_documentation/07-development-environment.adoc @@ -0,0 +1,71 @@ += Getting Ready to Maintain and Develop the Project +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +== Needed hardware: + +* Computer with any OS, +* USB to USB-C data cable compatible with your computer. + +== Needed software: + +* Arduino IDE with installed ESP-IDF plug-in, +* Telegram (smartphone app or its desktop version). + +== Preparing the software tools + +. Install Arduino IDE and add the ESP-IDF extension. User-friendly instructions on how to do it may be found here if you scroll down to the topic „*Installing Arduino IDE*“: +link:https://randomnerdtutorials.com/getting-started-with-esp32/[https://randomnerdtutorials.com/getting-started-with-esp32/#esp32-arduino-ide] + +The online instruction suggests to download and install the latest version, but the project was built using Arduino IDE version *1.8.19* and the „esp32“ board version *3.0.7*. Compatibility with the later versions was not tested, this is why it is recomended to use these, even though outdated, versions of the tools. + +. Create a folder for Arduino IDE projects. This folder will contain all the projects ever created in your Arduino IDE as well as all the installed libraries. The folder may be created anywhere on your computer and may be called any name you give to it (do not use spaces in the folder name, it may cause problems with the IDE). +Now, in your Arduino IDE, go to *Arduino > Settings *and at the top of the opened Settings window add the created folder path. +. Make sure all the required libraries from the „link:20-libraries.adoc[Libraries and their use]“ list are installed. To do so, in your Arduino IDE, go to *Arduino > Add library > Manage libraries*. In the opened window of the libraries manager you may find all the installed libraries as well as all the available libraries on the Internet. +It is recommended to install the libraries versions stated in the list even though they might be outdated. + +. Set the compilation target. The compiler has to be told what exact model of an ESP32 board to compile for. In case of this project it is „*XIAO_ESP32C3*“. To do so, in your Arduino IDE, go to *Tools > Boards > ESP32 Arduino > XIAO_ESP32C3*. + +== Opening the project + +. Open the folder for Arduino IDE projects (the one created in step 2 above) in your terminal and use the following command to get yourself a copy of the project: ++ +[source,bash] +---- +git clone https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign.git +---- + +. Open your Arduino IDE and go to *File > Projects > 42-Smart-Cluster-Sign > src*. Your project will open. +. The project comes without any security-sensitive credentials. They may be found printed on the back of the Sign or obtained from the Bocal team. Rename the „*credentials-example.h*“ file included in the project into „*credentials.h*“ and fill-in the credentials from the Sign. +WARNING: DO NOT COMPROMISE THE CONFIDENTIALITY OF THE CREDENTIALS !!! + +WARNING: REPORT ALL THE OCCURED LEAKS TO THE BOCAL TEAM IMMEDIATELY !!! + +== Uploading the changes + +There are 3 ways how to update the device: + +* using Arduino IDE and a USB-C data cable (described here), +* wirelessly using OTA update (described in link:08-cloud-pull-ota-updates.adoc[Updating the Program Using Cloud-Pull OTA]), +* using compiled binary, Terminal and a USB-C data cable (described in link:09-uploading-compiled-binary.adoc[Uploading the Program as Compiled Binary]). + +. Connect the Sign to your computer if you have not done so by this time. +. Activate the software update mode. To do that, on the back of the Sign locate button *B* and button *R*. First, press and hold button B. While holding button B, press and release button R once. Then release button B. Software update mode is now active. +. In Arduino IDE, go to *Tools* and set the following settings as follows: + +* Upload speed: 115200 +* CPU Frequency: 160 Mhz +* Flash Frequency: 80 Mhz +* Flash Mode: "QIO" +the fastest mode for the flash memory +* Partition Scheme: "Minimal SPIFFS" +do not use partition schemes marked with "No OTA" +* Core Debug Level: "Verbose" +the most detailed debugging output into the Serial monitor +* Erase All Flash Before Sketch Upload: "Disabled" +* Port: choose the development board port. + +. In Arduino IDE, click the Upload button to start uploading. diff --git a/docs/tech_documentation/08-cloud-pull-ota-updates.adoc b/docs/tech_documentation/08-cloud-pull-ota-updates.adoc new file mode 100644 index 0000000..51f4b27 --- /dev/null +++ b/docs/tech_documentation/08-cloud-pull-ota-updates.adoc @@ -0,0 +1,318 @@ += Updating the Program Using Cloud-Pull OTA +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +This instruction explains how to update the program running on the device using the cloud-pull OTA update system. + +First of all, just so you know, if you have physical access to the device, updating the Sign using Arduino IDE and a USB-C data cable (as described in link:07-development-environment.adoc[Getting ready to maintain and develop the project]) would be much faster than via OTA! + +Second of all, to use the cloud-pull OTA update described here, you have to be registered as a Collaborator in this GitHub repository: link:https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign[https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign] + +Ask your Bocal to contact the GitHub repository owner and request to register you. Add your GitHub public profile name to the request. Requests other than via your campus Bocal will be ignored due to the security reasons. + +OTA means “Over-The-Air”, in other words, that the device can update its own firmware without being connected to the computer with a USB cable. In this project we are using cloud-pull OTA system that works like that: with your computer you upload the new firmware file to the cloud (in our case the cloud is GitHub Releases), and the device downloads it by itself from the cloud. It is pretty much just like your phone or computer does it when it gets a software update. + +Before it downloads anything, the device receives update information from the manifest file stored in the GitHub repository. The manifest is a simple JSON file that tells the device which firmware version is available, where the firmware file is located, how large the file is and what SHA-256 hash the file must have. The firmware binary itself is stored in GitHub Releases. + +The device can start the OTA update check in three different ways: + +* by sending the OTA command through Telegram. +* by pressing the physical OTA button on the device. +* automatically, once a week, on predefined days of the month. +== Step-by-step update workflow — Ultra-short instructions version + +. Finish developing the new feature or bug fix. +. Increase the *SOFTWARE_VERSION* number in the *config.h* file. +. Compile the project in Arduino IDE into a firmware binary file. +. Open the OTA manifest file and find the correct device entry. +. Update the firmware version in the manifest. +. Find the firmware binary file in the *src* folder. Give it a clear name, including board name and version. Get the its exact size in bytes from the file Properties or *ls -la* command. +. Update the firmware file size in the manifest. +. Create a new GitHub Release with the matching version tag. +. Copy the firmware binary SHA-256 hash and update it in the manifest +. Copy the firmware binary download URL and update the manifest. +. Make sure „enabled“ is set to „true“. +. Commit and push the manifest changes. +. Trigger the OTA check through Telegram, the physical OTA button, or wait for the automatic scheduled check. +. Wait for the device to download the manifest and verify the data. +. Wait for the device to download the firmware from GitHub Releases. +. Wait for the device to install the firmware and reboot. +. Confirm that the device is running the new firmware version. +*Detailed update workflow – Full instructions version* + +The update process starts when you have just finished developing a new feature or fixing a bug and now want to upload those changes to the device. + +IMPORTANT: First thing first. Before compiling the project, go to the *config.h* file and make sure the program version number in the *SOFTWARE_VERSION* macro has been increased. Do not change the X.XX format of the version number! + +For example, if the device is currently running version 4.34, the new firmware should be version 4.35. The version number is important because the device uses it to decide whether it should update or not. If the version in the OTA manifest is the same as the version already running on the device, the device will not download the firmware again. If the version in the manifest is older than the version already running on the device, the device will also refuse to update. This protects the device from reinstalling the same firmware or accidentally downgrading itself. + +After increasing the version number, compile the project in Arduino IDE -- go to „Project“ and click on „Export compiled binary“. + +When the project compiles successfully, export the compiled binary file. This binary file is the actual firmware image that the device will download and install. + +Most likely you will find it in the *src* folder of the project on your computer. It will have a name something like *_src.ino.XIAO_ESP32C3.bin_*. + +The firmware file should have a clear name that includes the board and the version number. So, rename the firmware file into something nicer like firmware-xiao-esp32c3-v4.35.bin. This makes it easier to understand later which file belongs to which release. + +After exporting the binary file, check and write down its file size. It will be used in the manifest. + +WARNING: Do not use the project size that Arduino IDE says after the compilation is finished!!! Arduino IDE rounds up that number! + +Instead, go the the file properties of the compiled firmware binary file and get the exact size in bytes from there. OR use classic *ls -la* in the Terminal and you will see the file size will be in the middle column. + +The file size is needed because the device checks the size before downloading and flashing the firmware. If the firmware file is larger than the available OTA partition, the device refuses to update. This is done before downloading the whole file, so the device does not waste time, battery power, or network traffic on an update that cannot fit into flash memory. + +Create a new GitHub Release. Go to *https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign/releases* and click on „Draft a new release“. + +The release tag should match the firmware version. For example: v4.35. + +Describe the release by writing what is new in this version. + +Drag and drop the firmware file to the „Attach binary“ field. + +Click „Publish release“. + +Get the SHA-256 hash of the firmware file from GitHub Release. You will see it in GitHub Release next to the firmware binary file name. Copy it. + +The SHA-256 hash is a long hexadecimal value that uniquely identifies the exact content of the file. The device calculates the SHA-256 hash while downloading the firmware. After the download is complete, it compares the calculated hash with the hash you put in the manifest. If the hashes do not match, the device refuses to install the update. This protects the device from corrupted downloads and from accidentally installing a wrong firmware file. + +Get firmware binary file download link from GitHub Release. This link will be used in the manifest. + +Hover over the firmware binary file name with your mouse and click the Right mouse button. In the poped-up menu click on „Copy the link“. + +Edit the OTA manifest. Open the OTA manifest file in the repository. + +The manifest contains the update information used by the device. The manifest has a default section and a devices section. The default section is used when there is no special update entry for a specific device. The devices section is used to target individual devices. Each device can have its own firmware version, URL, size, SHA-256 hash, and enabled flag. The device checks its own device name or device ID and searches for a matching entry in the devices section. If it finds one, it uses that device-specific entry. If it does not find one, it uses the default entry. + +To update a specific device, change the entry for that device in the devices section. + +The device entry should contain the new version number, the GitHub Releases download URL, the SHA-256 hash, the firmware file size, and the enabled flag. + +The enabled flag must be set to true if the device is allowed to install this firmware. + +If enabled is false, the device will read the manifest but refuse to install that firmware. + +Before committing the manifest change, carefully check the following things. + +The version in the manifest must be higher than the version currently running on the device. + +The URL must point to the correct firmware binary in GitHub Releases. + +The SHA-256 hash must belong to that exact firmware binary. + +The size must match the exact size in bytes of that firmware binary. + +The enabled flag must be true for the device that should update. + +The device name or device ID in the manifest must match the device name or device ID used by the firmware. + +After checking the manifest, commit and push the manifest change to GitHub. + +At this point, the update is prepared. + +The new firmware file is available in GitHub Releases. + +The manifest points to the new firmware file. + +The device can now discover and install the update. + +Triggering the update with Telegram + +The first way to trigger the OTA update is through Telegram. This is the manual remote update method. Use this method when you want the device to check for updates immediately and you are not physically near the device. + +Send the */ota* command to the device in the Telegram chat. After receiving the command, the device sets the com_g.ota flag. The com_g.ota flag tells the main program that an OTA update check has been requested. The device then enters the normal OTA handling logic. The important detail is that Telegram itself does not directly download or install the firmware. Telegram only tells the device to start the update check. + +*_(OTA program logic description)_* + +Once the update check starts, the device connects to Wi-Fi if needed. Then it downloads the manifest from GitHub. After downloading the manifest, the device searches for its own device entry. If a device-specific entry exists, the device uses that entry. If no device-specific entry exists, the device uses the default entry. + +Then the device checks whether the selected entry is enabled. If enabled is false, the device stops the update process. If enabled is true, the device compares the version from the manifest with the version currently running on the device. If the manifest version is the same as the current version, the device does not download the firmware. If the manifest version is older than the current version, the device also does not download the firmware. If the manifest version is newer than the current version, the device continues. + +Next, the device checks the firmware size from the manifest. If the firmware is too large for the OTA partition, the device stops the update. If the firmware fits, the device starts downloading the binary file from GitHub Releases. + +While downloading the firmware, the device also calculates the SHA-256 hash. After the download is finished, the device compares the calculated SHA-256 hash with the SHA-256 hash from the manifest. If the hashes do not match, the device refuses to install the update. If the hashes match, the device finalizes the OTA update and prepares the new firmware for boot. + +Then the device reboots. After rebooting, the device should be running the new firmware version. + +To confirm the update, you may send /status command in Telegram or find the firmware version in the Serial output. If the device reports the new version number, the OTA update was successful. + +Triggering the update with the physical OTA button + +The second way to trigger the OTA update is by pressing the physical OTA button on the device. + +Use this method when you are physically near the device and want to force an update check without using Telegram. + +The physical OTA button does not contain a separate update system. Pressing the button simply changes the state of the com_g.ota flag. This is the same flag that is used when the OTA update is requested through Telegram. When the program later reaches the OTA handling logic, it sees that com_g.ota is active. Then it starts the same cloud-pull OTA update check. From this point onward, the process is identical to the Telegram-triggered update. + +The device connects to Wi-Fi if needed, downloads the manifest from GitHub, selects the correct manifest entry, checks whether the update is enabled, compares the manifest version with the current firmware version, checks whether the new firmware file fits into the OTA partition, downloads the firmware file from GitHub Releases, calculates and verifies the SHA-256 hash, installs the firmware, reboots. + +After the reboot, the device should be running the new firmware version. + +The physical OTA button is useful as a local manual fallback. If Telegram is unavailable, blocked, unstable, or temporarily not working, the update can still be triggered directly on the device. + +Triggering the update automatically once a week + +The third way to trigger the OTA update is automatic. The device checks for updates on specific predefined days of the month. The current logic is based on fixed days rather than storing the date of the last update check. In its current configuration, the device checks for updates automatically on the 3rd, 11th, 19th, and 27th day of each month. The exact days can be changed directly in the ota_handling() function in the ota.cpp file. + +The purpose of the automatic update check is to make sure the device can still update even if Telegram is not working and nobody presses the physical OTA button. This makes the update system more reliable. + +The automatic update check does not mean that the device will always download firmware on those days. It only means that the device will check the manifest. If there is no newer firmware version, the device does nothing. If the manifest points to the same version already running on the device, the device does nothing. If the manifest points to an older version, the device does nothing. + +Only if the manifest contains a newer enabled firmware entry for that device will the device continue with the update. When the automatic update condition is true, the device starts the same OTA handling logic used by Telegram and by the physical OTA button. + +The automatic update check may include a short random delay before connecting to GitHub. This prevents multiple devices from all checking the server at exactly the same moment. The delay should be short, for example less than one minute, because the device is battery-powered and should not waste energy. + +NOTE: The original document marked this section `// TO-DO: FIX THE TEXT FORMATTING`. + +What happens inside the device during the update + +When the update process starts, the first important file is the manifest. + +The manifest is a small text file in JSON format. + +The device downloads it from GitHub and reads it. + +The manifest does not contain the firmware itself. + +It only contains information about the firmware. + +The most important fields are the firmware version, the firmware download URL, the file size, the SHA-256 hash, and the enabled flag. + +The device uses the manifest to decide whether it should update. + +This is safer and more flexible than hardcoding the firmware URL directly into the device. + +If the firmware URL was hardcoded, the already-installed firmware would need to know the future firmware URL in advance. That would be inconvenient and fragile. + +With the manifest, the device only needs to know the manifest URL. The manifest can then be changed whenever a new release is created. + +After selecting the correct manifest entry, the device compares versions. + +The current firmware version is stored in the program itself. + +The target firmware version comes from the manifest. + +If the target version is not newer, the update stops. + +If the target version is newer, the device checks the firmware size. + +The ESP32-C3 uses two OTA application partitions. The currently running firmware is in one partition. The new firmware is written into the other partition. After the update is completed, the device reboots into the new firmware. + +This is why the firmware file must fit into the available OTA partition. + +If the size check passes, the device downloads the firmware binary from GitHub Releases. + +During the download, the device writes the firmware into the inactive OTA partition. + +At the same time, the device calculates the SHA-256 hash of the downloaded data. + +After the download is complete, the device compares the calculated hash with the hash from the manifest. + +If the hash is wrong, the update is aborted. + +If the hash is correct, the update is finalized. + +Then the device reboots. + +After rebooting, the ESP32 bootloader starts the newly installed firmware. + +How to confirm that the update worked + +After the device reboots, check the firmware version. + +The exact confirmation method depends on the current program features. + +If the device reports its version through Telegram, send the appropriate status or version command. + +If the device prints the version to the serial monitor during boot, open the serial monitor and check the boot message. + +If the device shows the version on the display, check the display. + +If the new firmware has a visible behavior change, confirm that the new behavior is present. + +A successful update means that the version reported by the device matches the version uploaded to GitHub Releases and written in the manifest. + +For example, if the device was running version 4.34, and the manifest pointed it to version 4.35, then after reboot the device should report version 4.35. + +If the device still reports version 4.34, the update did not complete or the manifest did not actually point to a newer firmware. + +Recommended release procedure + +First, finish the new feature or bug fix. + +Second, increase the firmware version number in the source code. + +Third, compile the project. + +Fourth, export the compiled binary. + +Fifth, check the binary file size. + +Sixth, calculate the SHA-256 hash of the binary. + +Seventh, create a new GitHub Release with the matching version tag. + +Eighth, upload the binary file to the GitHub Release. + +Ninth, copy the firmware download URL. + +Tenth, update the manifest with the new version, URL, file size, SHA-256 hash, and enabled flag. + +Eleventh, commit and push the manifest change. + +Twelfth, trigger the update using Telegram, the physical OTA button, or wait for the automatic weekly check. + +Thirteenth, wait for the device to download, verify, install, and reboot. + +Fourteenth, confirm that the device reports the new firmware version. + +Common reasons why the device may refuse to update + +The manifest version is the same as the current firmware version. + +The manifest version is older than the current firmware version. + +The enabled flag is false. + +The device name or device ID does not match the entry in the manifest. + +The firmware URL is wrong. + +The firmware file was not uploaded to GitHub Releases. + +The firmware size in the manifest is wrong. + +The firmware is too large for the OTA partition. + +The SHA-256 hash in the manifest does not match the downloaded file. + +The device has no Wi-Fi connection. + +The device has unstable power or low battery. + +GitHub is temporarily unreachable. + +The Telegram command was received, but the OTA check found no newer firmware. + +Important notes + +The device does not update just because a file exists in GitHub Releases. + +The device updates only when the manifest tells it to update. + +The manifest is the control point of the OTA system. + +GitHub Releases is only the storage location for firmware binaries. + +Telegram is only one of the ways to request an update check. + +The physical OTA button is another way to request the same update check. + +The automatic weekly update is a fallback way to request the same update check. + +All three methods use the same cloud-pull OTA logic. + +A successful OTA update is confirmed only after the device reboots and reports that it is running the new firmware version. diff --git a/docs/tech_documentation/09-uploading-compiled-binary.adoc b/docs/tech_documentation/09-uploading-compiled-binary.adoc new file mode 100644 index 0000000..56593b8 --- /dev/null +++ b/docs/tech_documentation/09-uploading-compiled-binary.adoc @@ -0,0 +1,45 @@ += Uploading the Program as Compiled Binary +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +There is a third way of how you can upload your program into the memory of the device — compile the program into a .bin file and then upload it manually in the Terminal. This method is overly complicated, so do not use it unless necessary. In 99,9% of cases the first method – described above in the *Uploading the changes* topic of link:07-development-environment.adoc[Getting ready to maintain and develop the project] – is the best way to go. This third method is good in two cases: for some reason you cannot compile the project on this computer but it compiled on another computer; you want to show off your cool skill of flashing microcontrollers with a bare command line in your Terminal. + +== The Uploading Process + +So, you have got your .bin file. Let’s flash it into the Sign’s memory. Read all the steps first, before working on them. + +. For this process you will need a CLI software tool called *esptool.py*. If you have already installed Arduino IDE and its ESP-IDF extension, you probably already have esptool.py as well. To confirm that, let’s find esptool.py on your computer. On macOS, esptool.py is usually located here: +*~/Library/Arduino15/packages/esp32/tools/esptool_py/ > Version-numbered folder > esptool* +In my case the full path looked like this: +*MacintoshHD/Users/romanalexandrov/Library/Arduino15/packages/esp32/tools/esptool_py/4.6/esptool* + +If you see esptool on your computer, that’s great. Note down the path to the esptool executable and go from here straight to the step 2. + +If you cannot find esptool, Install Arduino IDE and add the ESP-IDF extension. This way esptool will get installed as well. User-friendly instructions on how to do it may be found here if you scroll down to the topic „*Installing Arduino IDE*“: +link:https://randomnerdtutorials.com/getting-started-with-esp32/[https://randomnerdtutorials.com/getting-started-with-esp32/#esp32-arduino-ide] + +The online instruction suggests to download and install the latest version, but the project was built using Arduino IDE version *1.8.19* and the „esp32“ board version *3.0.7*. Compatibility with the later versions was not tested, this is why it is recomended to use these, even though outdated, versions of the tools. + +Once you are done installing Arduino IDE and its ESP-IDF extension, go and find esptool on your computer. + +. Connect the Sign to your computer if you have not done so by this time. +. Activate the software update mode. To do that, on the back of the Sign locate button *B* and button *R*. First, press and hold button B. While holding button B, press and release button R once. Then release button B. Software update mode is now active. +. Open your Terminal and run the command *ls /dev/cu.** to find wich port the Sign is connected to. In the results, look for somethig like /dev/cu.usbmodemXXXX or /dev/cu.usbserial-XXXX or /dev/cu.esp32c3. Once you found the port name, note it down as we will need it later. +. In the Terminal, navigate your way into the folder where your .bin file is located. To start uploading the .bin file into the Sign, you will need the following command: + +~/Library/Arduino15/packages/esp32/tools/esptool_py/*/esptool \\ +--chip esp32c3 \\ +--port YOUR_PORT_NAME \\ +--baud 115200 \\ +write_flash 0x10000 YOUR_BIN_FILE_NAME.bin + +In the command you should change YOUR_PORT_NAME to the port name you acquired from the step 4. For example: +--port /dev/cu.usbmodem1101 +You should change YOUR_BIN_FILE_NAME.bin to the name of the .bin file. For example: +write_flash 0x10000 42-Smart-Cluster-Sign.ino.XIAO_ESP32C3.bin +. Once you hit „Enter“, the upload will begin. Do not worry if you encounter errors. esptool usually gives very discriptive explanations to errors. In the worst case scenario, ChatGPT may help you. +. When the upload is finished, press the *R* button on the back of the Sign once — that will reboot the Sign and start the execution of the uploaded program. + Done. You are amazing. diff --git a/docs/tech_documentation/10-firmware-rollback.adoc b/docs/tech_documentation/10-firmware-rollback.adoc new file mode 100644 index 0000000..1e80ce9 --- /dev/null +++ b/docs/tech_documentation/10-firmware-rollback.adoc @@ -0,0 +1,544 @@ += Everything about the Firmware Rollback Functionality +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +. *Overview* +The firmware rollback functionality is a safety mechanism designed to protect the device from becoming unusable after a faulty OTA update. + +The project already supports Cloud-pull OTA updates. This means the device can download a new firmware binary from GitHub Releases, verify it, write it to the inactive OTA partition, and reboot into it. + +However, OTA updates introduce an important risk. + +If the newly installed firmware contains a serious bug, the device may crash, stall, or enter a reboot loop before it becomes possible to send another OTA update. If the device is physically inaccessible, this could make recovery impossible. + +The firmware rollback functionality reduces this risk. + +Its purpose is to detect that a newly installed firmware did not complete a normal execution cycle and automatically switch the device back to the previous OTA partition which contains last stable firmware version. + +In simple terms: + +The device installs a new firmware. + +The new firmware is given one chance to run correctly. + +If the new firmware successfully reaches the normal sleep stage, it is considered safe. + +If the new firmware crashes or stalls before completing the cycle, the next boot switches back to the previous firmware partition. + +This functionality acts as a custom software rollback system. + +It was implemented because the native ESP-IDF bootloader rollback mechanism was not active in the Arduino IDE environment used by this project. After OTA updates, the newly installed firmware was observed to boot as ESP_OTA_IMG_VALID instead of ESP_OTA_IMG_PENDING_VERIFY. Because of that, the standard ESP-IDF rollback confirmation mechanism could not be used as expected. + +. *Why Firmware Rollback Is Needed* +OTA updates are powerful, but they are dangerous if there is no recovery mechanism. + +A normal OTA update can successfully install a broken firmware. The installation process may be completely correct: the file may download correctly, the SHA-256 hash may match, the firmware may be written to flash correctly, and the device may reboot successfully. + +The problem appears after the reboot. + +For example, the new firmware may contain a bug that causes: + +a CPU panic, + +an illegal instruction fault, + +a watchdog reset, + +an infinite loop, + +a crash before Telegram starts, + +a crash before Wi-Fi connects, + +a crash before the OTA logic can run again. + +In such a situation, the device may repeatedly reboot into the same broken firmware. + +Without rollback, the device could be permanently stuck until someone physically connects it to a computer and flashes healthy firmware through USB. + +This is exactly the situation rollback is meant to prevent. + +. *Native ESP32 Rollback Limitation* +The ESP32 OTA system normally supports bootloader-level rollback. + +In the native ESP-IDF rollback model, a newly installed firmware is first booted in a pending verification state. The firmware must then confirm that it works. If it crashes before confirming itself, the bootloader can automatically return to the previous firmware. + +This project investigated that mechanism. + +The device has a correct OTA partition layout with two OTA application partitions: + +app0 + +app1 + +It also has the OTA data partition required for switching between OTA slots. + +However, in the Arduino IDE environment used by the project, the newly installed firmware booted directly as valid. It did not boot in pending verification mode. + +The observed state after OTA was: + +ESP_OTA_IMG_VALID + +The expected state for native bootloader rollback would have been: + +ESP_OTA_IMG_PENDING_VERIFY + +Because the firmware was already marked valid before the application code started, functions such as esp_ota_mark_app_valid_cancel_rollback() and esp_ota_mark_app_invalid_rollback_and_reboot() could not provide the expected automatic rollback behaviour. + +For this reason, the project implements its own software rollback mechanism. + +. *High-Level Rollback Idea* +The custom rollback system is based on three main ideas: + +a persistent rollback flag stored in LittleFS, + +reset reason detection, + +manual switching between OTA partitions. + +The rollback flag is stored in a LittleFS file. It survives resets that erase RTC memory as well as firmware updates. The reset reason is used to decide whether the previous reset looked like a real firmware failure. + +The OTA partition switching is done with ESP-IDF partition APIs. If the current firmware appears to be faulty, the device sets the other OTA application partition as the next boot partition and restarts. + +The simplified logic is: + +If the firmware is marked verified, it is allowed to start a test run. + +At the beginning of the run, the firmware marks itself untrustworthy. + +If the firmware reaches normal sleep, it marks itself verified again. + +If the firmware crashes before normal sleep, it remains marked untrustworthy. + +On the next boot, if the firmware is untrustworthy and the reset reason indicates a crash or watchdog reset, the device switches to the other OTA partition. + +. *Meaning of the Rollback States* +The rollback flag uses the FIRMWARE_t enum: + +[source,c] +---- +typedef enum { +VERIFIED = false, +UNTRUSTWORTHY = true +} FIRMWARE_t; +---- + +The names describe the current trust state of the firmware. + +VERIFIED means that the firmware is allowed to run. + +UNTRUSTWORTHY means that the previous run did not complete successfully, or that the rollback state could not be safely read. + +The logic intentionally uses a negative state name. + +This is important because boolean and RTC-style globals are false by default on the first run after USB flashing. By making false mean VERIFIED, the first wired firmware upload does not immediately trigger rollback. + +. *Persistent Rollback Flag in LittleFS* +The rollback flag is stored in a LittleFS file: + +/defective_firmware.txt + +The file contains one character: + +0 means VERIFIED. + +1 means UNTRUSTWORTHY. + +LittleFS is used because RTC memory did not survive all relevant reset types on the tested device. Manual reset, core panic, and ESP.restart() were observed to reset RTC globals. Therefore, RTC memory was not reliable enough for rollback state storage. + +LittleFS is more suitable because it stores the rollback state in flash memory. Reads do not meaningfully wear flash memory. Writes do wear flash memory, but the rollback flag is written only rarely: + +when the firmware starts a verification run, + +when the firmware successfully completes the run, + +when rollback is about to switch partitions, + +before OTA reboot. + +This is a very small number of writes compared with the lifetime of the flash memory. + +LittleFS also provides wear-leveling behaviour, which reduces the risk of repeatedly wearing the same physical flash sector. + +. *Aggressive Fallback on LittleFS Failure* +The rollback flag reading function is intentionally conservative. + +If the flag file cannot be found or opened, the function returns UNTRUSTWORTHY. + +This is intentional. + +The rollback system treats an unavailable rollback flag as a potentially unsafe state. + +The reasoning is: + +If the firmware is broken and LittleFS also fails, the rollback mechanism should not silently trust the firmware. + +Instead, the reset reason detection is allowed to make the final decision. + +This creates a second layer of protection. LittleFS state is the primary rollback state, but reset reason detection acts as an additional safety check. + +. *Reset Reason Detection* +Not every reset means that the firmware is defective. + +The device can reset for normal or harmless reasons. + +For example: + +manual reset, + +software restart, + +power-on reset, + +deep sleep wake-up, + +brownout or power instability. + +Rollback should not happen for all of these. + +The rollback system checks the reset reason using esp_reset_reason(). + +Only suspicious reset reasons are considered rollback candidates. + +The suspicious reset reasons are: + +ESP_RST_PANIC + +ESP_RST_TASK_WDT + +ESP_RST_INT_WDT + +ESP_RST_WDT + +These reset reasons indicate that the previous firmware run likely crashed, stalled, or failed to feed the watchdog. + +Non-suspicious reset reasons do not immediately cause rollback. + +This prevents normal behaviour from being mistaken for firmware failure. + +. *OTA Partition Switching* +The ESP32-C3 uses two OTA application partitions: + +app0 + +app1 + +At any given time, the device is running from one of them. + +When a rollback is required, the firmware selects the other OTA partition as the next boot partition. + +For example: + +If the device is currently running from app0, rollback selects app1. + +If the device is currently running from app1, rollback selects app0. + +The switching is performed using ESP-IDF APIs: + +esp_ota_get_running_partition() is used to find the currently running application partition. + +esp_partition_find_first() is used to find the other OTA application partition. + +esp_ota_set_boot_partition() is used to tell the bootloader which application partition should boot next. + +After the boot partition is changed, the device restarts. + +On the next boot, the bootloader starts the selected partition. + +. *Rollback Function Behaviour* +The main rollback function runs early during boot, after LittleFS has been initialized. + +This timing is important. + +It must run early enough to catch a faulty firmware before the device reaches dangerous or unstable code. + +It cannot run before LittleFS is initialized, because the rollback flag is stored in LittleFS. + +The function first checks whether the firmware has already been verified during the current run. + +If the firmware has already been verified, the function returns immediately. + +Then the function reads the rollback flag from LittleFS. + +If the flag says VERIFIED, the firmware is allowed to run. The function changes the flag to UNTRUSTWORTHY and returns. + +This gives the firmware one full run cycle to prove itself. + +If the firmware reaches the end of the normal cycle and enters sleep, it will change the flag back to VERIFIED. + +If the firmware crashes before reaching sleep, the flag remains UNTRUSTWORTHY. + +On the next boot, the function reads the flag again. + +If the flag says UNTRUSTWORTHY, the function checks the reset reason. + +If the reset reason is not suspicious, rollback is skipped. + +If the reset reason is suspicious, the function switches to the other OTA partition and restarts the device. + +. *Successful Firmware Run* +A firmware run is considered successful only when the device reaches the end of its normal execution cycle. + +For this project, that means the firmware reaches the final stage before deep sleep. + +At that point, the device has already completed the important parts of its runtime logic. + +The firmware has booted. + +The filesystem has initialized. + +The device state has been restored. + +The battery logic has run. + +The main program logic has run. + +The display logic has had a chance to run. + +The device has reached the point where it is ready to sleep normally. + +Only then is the firmware marked VERIFIED again. + +This is done near the end of the sleep function, before the device enters deep sleep. + +This placement is important. + +If the firmware were marked verified too early, a later crash in the same cycle would not trigger rollback. The whole purpose of the rollback mechanism is to make sure the firmware proves that the entire execution cycle can complete. + +. *Failed Firmware Run* +A firmware run is considered failed if it does not reach the normal sleep stage. + +For example, the firmware may: + +crash with a CPU panic, + +execute an illegal instruction, + +stall until the watchdog resets it, + +enter a reboot loop, + +fail before reaching the end of the cycle. + +If the firmware fails before normal sleep, the rollback flag remains UNTRUSTWORTHY. + +After reboot, the rollback function runs again. + +If the previous reset reason was suspicious, the function assumes that the current firmware is defective. + +Then it switches the boot partition to the other OTA application partition and restarts the device. + +This restores the previous firmware version, assuming the previous partition still contains a working firmware. + +. Interaction with OTA Updates +The rollback system works together with the Cloud-pull OTA update system. + +When a new firmware is installed through OTA, it is written to the inactive OTA partition. + +For example: + +If the device is running from app0, the new firmware is written to app1. + +If the device is running from app1, the new firmware is written to app0. + +After the OTA update completes successfully, the device reboots into the new firmware. + +The rollback flag is prepared so that the new firmware gets one chance to run. + +If the new firmware completes the full cycle, it is marked verified. + +If the new firmware crashes or stalls, the next boot switches back to the previous partition. + +After rollback, the broken firmware remains in the inactive partition. + +A later OTA update can overwrite that broken partition with a corrected firmware version. + +. What Rollback Protects Against +The rollback system protects against bugs that happen after the rollback check is able to run. + +It can protect against: + +crashes in the main firmware logic, + +watchdog resets, + +illegal instruction crashes after startup, + +logic bugs that prevent reaching sleep, + +firmware versions that stall before completing the run, + +broken OTA versions that cannot reach the next update check. + +This is the main risk the system is designed to reduce. + +. What Rollback Does Not Protect Against +This is a software rollback mechanism, not true bootloader-level rollback. + +It cannot protect against every possible failure. + +It does not protect against failures that happen before the rollback logic can run. + +For example, it may not protect against: + +a crash before setup starts, + +a crash in global/static object initialization, + +a crash before LittleFS initialization, + +a bootloader problem, + +a corrupted partition table, + +both OTA partitions containing broken firmware, + +physical flash failure, + +power loss during critical flash operations. + +Native bootloader rollback would provide stronger protection, but it was not active in the Arduino IDE environment used by this project. + +The implemented rollback system is therefore a practical Arduino-compatible fallback. + +. Why the Other Partition Is Used +The ESP32 OTA layout stores two firmware images. + +Only one is currently running. + +The other one usually contains the previous firmware or the next firmware being written. + +When the device updates successfully, the new firmware becomes the running firmware, and the old firmware remains in the other partition. + +This makes rollback possible. + +If the new firmware fails, the device can switch back to the other partition. + +The bootloader then starts the previous firmware again. + +This is why the project does not erase the previous firmware immediately after an OTA update. + +. Behaviour After Rollback +After rollback, the device restarts into the other OTA partition. + +This should normally be the previous stable firmware. + +The rollback state is cleared so that the device does not bounce endlessly between app0 and app1. + +The recovered firmware can then continue normal operation. + +If a corrected firmware version is later uploaded to GitHub Releases and written into the manifest, the device can update again. + +The next OTA update will normally overwrite the inactive partition, which may currently contain the broken firmware. + +. Difference Between Verification and SHA-256 +The project uses two different safety checks: + +SHA-256 verification, + +firmware rollback verification. + +These checks solve different problems. + +SHA-256 verification checks that the downloaded firmware file is exactly the expected file. + +It protects against corrupted downloads, wrong files, or mismatched release assets. + +Rollback verification checks that the firmware actually works after it has been installed. + +A firmware file can pass SHA-256 verification and still contain a serious bug. + +Therefore, SHA-256 verification is not enough by itself. + +SHA-256 proves that the file is correct. + +Rollback proves that the firmware can run successfully. + +. Difference Between Version Checking and Rollback +Version checking prevents unnecessary or incorrect updates. + +The device compares the currently running firmware version with the version listed in the manifest. + +If the manifest version is not newer, the device does not update. + +Rollback solves a different problem. + +Rollback deals with what happens after a newer firmware was successfully installed but turned out to be defective. + +Version checking decides whether to install. + +Rollback decides whether the installed firmware is safe to keep. + +. Testing Performed +The rollback system was tested using intentionally defective firmware. + +A healthy firmware version containing the rollback functionality was installed on the device. + +Then a new firmware version was built with an intentional critical bug. + +The defective firmware was uploaded to GitHub Releases and installed through the Cloud-pull OTA system. + +After the OTA update, the device booted into the defective firmware. + +The defective firmware crashed. + +The rollback mechanism detected that the previous run did not complete successfully. + +The rollback mechanism switched the boot partition to the other OTA application partition. + +The device restarted. + +The previous healthy firmware booted successfully. + +This confirmed that the custom rollback system works as intended. + +. Safety Notes +The rollback code is safety-critical. + +A bug in this code could prevent future OTA updates or cause the device to boot the wrong partition. + +For this reason, the rollback implementation should not be modified casually. + +Any change to this file should be tested with physical USB access to the device. + +The most important rules are: + +Do not mark firmware verified too early. + +Do not switch partitions unless the reset reason is suspicious. + +Do not erase the other OTA partition during normal operation. + +Do not assume RTC memory survives all reset types. + +Do not rely on native ESP-IDF rollback unless the bootloader actually boots new OTA images as pending verification. + +Do not test rollback only with successful firmware. + +Always test rollback with intentionally defective firmware before trusting changes. + +. Summary +The firmware rollback functionality provides an additional safety layer for the Cloud-pull OTA update system. + +It prevents a faulty OTA firmware update from permanently trapping the device in a crash loop. + +Because native ESP-IDF bootloader rollback was not active in the Arduino IDE environment, the project implements its own software rollback system. + +The custom rollback system uses: + +a LittleFS rollback flag, + +reset reason detection, + +manual OTA partition switching, + +full-cycle firmware verification. + +A new firmware is trusted only after it completes a normal execution cycle and reaches the final sleep stage. + +If it crashes or stalls before that, the next boot switches back to the previous OTA partition. + +This makes the OTA update system significantly safer and reduces the risk of remotely bricking the device. diff --git a/docs/tech_documentation/11-hardware-maintenance.adoc b/docs/tech_documentation/11-hardware-maintenance.adoc new file mode 100644 index 0000000..b9c0476 --- /dev/null +++ b/docs/tech_documentation/11-hardware-maintenance.adoc @@ -0,0 +1,26 @@ += Hardware Maintenance +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +The Sign generally does not require any assistance, but it needs to be charged once every so often. If you have not ever charged the Sign before, it is OK, since its high-capacity inner battery allows the Sign to operate up to half a year on a single charge. + +If the Sign needs to be recharged, it will indicate it by displaying the „LOW BATTERY“ note next to the cluster number as well as sending a notification into its Telegram chat. + +Needed hardware: + +* *5V* power adapter, +* USB-C cable, compatible with the power adapter. +To charge the Sign: + +. locate a round opening on the side of the wooden frame; +. look inside the opening - there you should see a USB-C female connector; +. find a cable with a USB-C male connector that fits the round opening; +. if the cable is too short to reach the power socket, carefully take the Sign off the wall. +To do that use both of your hands! Grab the wooden frame from its top and its bottom – that will prevent it from falling. Do not pull the Sign from the wall with your arms. Instead start pushing your fingers deeper between the frame and the wall – at some point the Sign will simply detach from the wall and stay in your hands; +. connect the Sign to the 5V power adapter with the cable; +. plug the 5V power adapter into a power socket; +. look at the opening in the frame again - you should see red light comming out of it, which means that the Sign is now charging. If after a few seconds there is no red light, that may indicate a poor connection or lack of electricity. +. wait for the red light to turn off – typically about 10 hours – that indicates that the Sign is fully charged. diff --git a/docs/tech_documentation/12-functions-reference.adoc b/docs/tech_documentation/12-functions-reference.adoc new file mode 100644 index 0000000..ec25dca --- /dev/null +++ b/docs/tech_documentation/12-functions-reference.adoc @@ -0,0 +1,291 @@ += Functions Reference +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +[#program-files-description] + +== Program Files Description + +[cols="1,2"] +|=== +|src.ino +|Main file. Despite being written in C, it has to have the .ino extention as it is the Arduino IDE file format. Other source files may have other extentions. The file name has to be the same as the folder name it is contained in – as you may see in this project. + +|42-Smart-Cluster-Sign.h +a| +Main header file that includes all necessary libraries and declares functions used across the project. + +IMPORTANT: Including ota.h at the bottom of the file is not coinsidential – it has to be kept below the OTA functions declarations for the OTA update to work. + +|battery_management.cpp +|Initialize inner ADC module, measure battery voltage level, assign the results to a battery state, act upon the battery state. + +|bitmap_library.h +|Contains all the images to be displayed on the device screen in bit-map form. The complete images list can be found in the file. + +|buttons_handling.cpp +|Buttons initialisation and interrupt service routines. + +|cluster_number_mode.cpp +|Everything that the Sign does outside of the exam time: gets exact time, checks exams, displays system warnings if there are any, displays cluster number. + +|config.h +|Constants to adjust and tune the program behaviour. E.g. software version number, device name, DEBUG macro, Serial port baud rate, the Sign’s wake-up hours, Wi-Fi connection time limit, etc. More about it in the *link:12-functions-reference.adoc#the-config-file[The config file] *chapter. + +|constants.h +|Constants that are not expected to be ever changed. General constants, buttons and display SPI port configurations, display driver coniguration, images and errors enumerators. + +|credentials.h +|Contains project confidential information, such as Intra API authorisation data, Telegram bot token, Wi-Fi access point SSID and password. If instead of credentials.h there is only credentials-example.h, then in the *link:07-development-environment.adoc[Getting ready to maintain and develop the project]* chapter, please, navigate to „Opening the project“, point 3. + +|display_handling.cpp +|Functions for outputting images and text onto the display, cluster number drawing logic, display initialization. Using display.powerOff() in the drawing functions may trigger the watchdog with high probability. + +|exam_mode.cpp +|Everything to handle informing students about an exam and the pre-exam time. + +|file_system.cpp +|Manages file system operations, including reading from and writing to files. + +|globals.h +|Declares global variables and includes necessary libraries. + +|globals.cpp +|Defines global variables used across the project. + +|intra_interaction.cpp +|Handles interactions with the 42 Intra API, including fetching exam schedules. + +|ota.h +|Manages OTA updates, including initializing and handling OTA update processes. + +|utils.cpp +|Contains functions like sleep, delay, Wi-Fi connection, serial initialisation and exam simulation. + +|power_down_recovery.cpp +|Handles power-down recovery, including reporting reboot reasons and calling for viable variables check. + +|telegram_bot.cpp +|Manages interactions with the Telegram bot, including checking and responding to messages. + +|telegram_compose_message.cpp +|Composes messages to be sent via the Telegram bot. + +|time_utilities.cpp +|Contains time-related utilities, including fetching and calculating time. + +|watchdog.cpp +|Manages the watchdog timer, including starting, stopping, and resetting the watchdog. +|=== + +[#the-config-file] + +== The Config File + +The config.h file contains configurable parameters for tuning the software behavior of the 42 Smart Cluster Sign project. This file allows you to adjust various settings, including software version, device name, debugging options, and time-related configurations. To customize the behavior of the 42 Smart Cluster Sign project, modify the values in the config.h file according to your requirements. Ensure that the changes you make are consistent with the overall project requirements and do not conflict with other configurations. + +*SOFTWARE_VERSION*: Defines the current version of the software. The software version number can be seen in the debugging output in the Serial monitor as well as in the Telegram chat message when the Sign receives the „/status“ command. The software version number is an essential tool for tracking bugs and the program installed on the physical device. + +[source,c] +---- +#define SOFTWARE_VERSION 4.32 +---- + +*DEVICE_NAME*: Specifies the name of the device, that can be seen in the Ports list when updating via OTA. + +[source,c] +---- +#define DEVICE_NAME "42 Prague C3 Smart Sign" +---- + +*DEBUG* macro: Enables or disables serial output for debugging. Comment out the „#define DEBUG“ line to turn off serial output or uncomment it to turn it on again. The macro does not affect the Core Debug Level, which you set in the Arduino IDE Tools. Changing the DEBUG macro and/or the Core Debug Level will change the dynamic of the whole program execution which might introduce new bugs or solve existing ones. + +[source,c] +---- +#define DEBUG +#ifdef DEBUG +#define DEBUG_PRINTF(...) Serial.printf(__VA_ARGS__) +#define WD_RESET_INFO true +#else +#define DEBUG_PRINTF(...) +#define WD_RESET_INFO false +#endif +---- + +*EXAM_SIMULATION* macro: Uncomment this line to simulate an exam starting at specified time. Useful for testing exam mode execution when there are no actual exams. The macro injects fictitious information about an exam (by default, scheduled for today from 18:00 till 21:00, 4 students attending) into the message from Intra, which causes the Sign to believe that there is an actual exam that day. If needed, the fictitious exam information may be changed in the exam_simulation() function, located in the utils.cpp file. + +CAUTION: *_When you are finished with the tests, do not forget to comment out this line and flash the software onto the Sign again to turn off the simulation. Otherwise the Sign will be showing the fictional exam every day._* + +[source,c] +---- +#define EXAM_SIMULATION +---- + +*GCC Optimization* macro: Lets you expicitly tell the compiler how to optimise the program code. Be adviced that different levels of optimisation may influence the dynamic of the whole program execution in different ways. In practice, it means that one level of optimisation may introduce new bugs to the project; another level of optimisation may solve those bugs, but introduce completely new ones. Settle on one level of optimisation before starting debugging the project. + +* O0 — no optimization (default for the project); +* O1 — basic optimization; +* O2 — moderate optimization: slight program performance increase; +* Os — optimize for size: O2 level + optimizations to reduce program size; +* O3 — high-level optimization: better performance, but bigger program size; +* Ofast — optimize for speed: highest possible performance but may break standards compliance; +* Og — optimize for debugging. +[source,c] +---- +#pragma GCC optimize ("O0") +---- + +*BAUD_RATE*: Sets the speed of the serial communication. + +[source,c] +---- +#define BAUD_RATE 115200 +---- + +*WAKE_UP_HOURS*: Defines the hours at which the Sign should wake up and check if there are any new exams. The format is a comma-separated list of hours (24-hour format), e.g. 9 means „at 9:00“ (9AM), 18 means „at 18:00“ (6PM), and so on. Note, that every hour is separated by a comma, but there is no comma after the last wake-up hour. Adhere to this format when removing or adding wake-up hours. You may change the existing wake-up hours, delete them or add new wake-up hours. There always should be at least one wake-up hour. Maximum number of wake-up hours is 24. Please, keep in mind that there is no such time as 24:00, instead use 0 (for 0:00) if you want the Sign to wake up at midnights. + +[source,c] +---- +#define WAKE_UP_HOURS 6, 9, 12, 15, 18, 21 +---- + +*RETRIES_LIMIT*: Sets the maximum number of retries for getting time and exam information. Be adviced, that every next retry is 5 minutes longer than the previous one. E.g. the 1st retry will occur 5 minutes after the default try, the 2nd retry will occur 10 minutes after the 1st retry, the 3rd retry will occur 15 minutes after the 2nd retry, the 4th retry will occur 20 minutes after the 3rd retry, the 5th retry will occur 25 minutes after the 4th retry, and so forth. It is recomended not to exceed the number of 5 retries. + +[source,c] +---- +#define RETRIES_LIMIT 3 +---- + +*TIME_ZONE*: Specifies the campus time zone according to the GMT / UTC standard. *_IMPORTANT:_* If in the country of the campus location it is common to switch between winter time and summer time, use the WINTER TIME time zone only! Include "-" sign if it applies to the time zone of your cluster. Do not include "+" sign. + +[source,c] +---- +#define TIME_ZONE 1 +---- + +*CONNECT_TIMEOUT_S*: Sets the timeout for Wi-Fi connection attempts (in seconds). + +[source,c] +---- +#define CONNECT_TIMEOUT_S 5 +---- + +*DEBOUNCE_DELAY_MS*: Defines the debounce delay for button presses (in milliseconds). The lower the number, the faster the button responds, but the probability of errors to occur grows. + +[source,c] +---- +#define DEBOUNCE_DELAY_MS 1000ul +---- + +*WD_TIMEOUT_MS*: Sets the timeout for the watchdog timer (in milliseconds). Limited to a maximum timeout of 8 seconds (8000 milliseconds). + +[source,c] +---- +#define WD_TIMEOUT_MS 8000 +---- + +*OTA_WAIT_LIMIT_S*: Defines the maximum wait time for OTA updates (in seconds). + +[source,c] +---- +#define OTA_WAIT_LIMIT_S 600 +---- + +[#functions-description] + +== Functions Description + +== main file functions + +setup(): initializes various components, including the watchdog timer, display, file system, buttons, battery, power-down recovery, battery check, Telegram bot, and OTA updates. + +loop(): handles the OTA update waiting loop and calls the pathfinder() function to determine the next action based on the current status. + +pathfinder(): the function determines the next action based on the current status, including handling exam mode and cluster number mode, and then puts the device to sleep for the calculated time. The function deliberately uses two if-statements since the exam_status may change its value inside the first if-statement. + +== Display Handling + +draw_text(String output, uint16_t x, uint16_t y): Draws text on the display at the specified coordinates. + +draw_exam_start_time(): Draws the exam start time on the display. + +draw_bitmap_partial_update(const unsigned char* image, uint16_t width, uint16_t height): Draws a partial bitmap image on the display. + +draw_colour_bitmap(const unsigned char* black_image, const unsigned char* red_image): Draws a full-color bitmap image on the display. + +draw_bitmap_full_update(const unsigned char* image, uint16_t width, uint16_t height): Draws a full bitmap image on the display. + +display_cluster_number(IMAGE_t mode): makes decisions of whether to draw something on the display or not to draw; calls drawing functions when necessary. REFACTORING RECOMENDED. If you refactor the function, please, do not forget to change the documentation accordingly. + +clear_display(): Clears the display. + +display_init(): Initializes the display. + +== Exam Mode + +exam_mode(): Handles the exam mode, including displaying exam-related messages and images. + +== File System + +secret_verification(String input): Verifies the secret token. + +data_restore(const char* file_name): Restores data from the specified file. + +data_integrity_check(): Checks the integrity of the file system and restores necessary data. + +write_to_file(const char* file_name, char* input): Writes data to the specified file in the FS. + +read_from_file(const char* file_name, char* output): Reads data from the specified file in the FS. + +file_sys_init(): Initializes the File System. + +== Intra Interaction + +fetch_exams(): Fetches exam schedules from the 42 Intra API. + +== Telegram Bot + +telegram_check(): Checks for new messages from the Telegram bot and handles them. + +compose_message(int32_t subject, int16_t days_left): Composes messages to be sent via the Telegram bot. + +== Time Utilities + +expiration_counter(): Calculates the number of days left until the secret token expires. + +unix_timestamp_decoder(uint8_t* p_day, uint8_t* p_month, uint16_t* p_year): Decodes a UNIX timestamp into day, month, and year. + +get_time(): Fetches the current time from an NTP server. + +time_till_wakeup(): Calculates the time until the next wake-up. + +time_till_event(int8_t hours, uint8_t minutes): Calculates the time until a specified event. + +time_sync(unsigned int preexam_time): Synchronizes time before an exam. + +== Watchdog + +watchdog_start(): Starts the watchdog timer. + +watchdog_reset(): Resets the watchdog timer. + +watchdog_stop(): Stops the watchdog timer. + +watchdog_init(): Initializes the watchdog timer. + +== Other Functions + +go_to_sleep(uint64_t time_in_millis): Puts the device to sleep for the specified time. + +ft_delay(uint64_t time_in_millis): Delays execution and puts the device into light sleep. This function helps saving battery power by turning off the Wi-Fi and the Bluetooth modules of the device. Do not use it there where you need to maintain wireless connection. + +wifi_connect(): Connects to Wi-Fi. + +serial_init(): Initializes the serial communication. + +power_down_recovery(): Handles power-down recovery, including reporting reboot reasons. + +exam_simulation(): Creates a fictitious exam information for the EXAM_SIMULATION macro. diff --git a/docs/tech_documentation/13-architectural-decisions.adoc b/docs/tech_documentation/13-architectural-decisions.adoc new file mode 100644 index 0000000..7716365 --- /dev/null +++ b/docs/tech_documentation/13-architectural-decisions.adoc @@ -0,0 +1,143 @@ += Architectural Decisions Explained +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +Even though the program code of the Sign is pretty straight forward, there are some architectural solutions that are not that clearly obvious. Here we will try to address these uncertainties and make sure the program code is fully understandable. Be adviced, that the article assumes that you have already seen the source code as well as read some previous articles here, namely link:04-program-run-overview.adoc[General description of the program run], link:05-program-run-step-by-step.adoc[Program run step-by-step], link:12-functions-reference.adoc#program-files-description[Program files description], link:12-functions-reference.adoc#the-config-file[The config file], link:12-functions-reference.adoc#functions-description[Functions description], link:14-intra-api.adoc[How to get exams info from Intra]. + +== src.ino file + +If you are new to Arduino IDE, setup() and loop() functions are standart for this workframe. Treat them as you would treat main() in a .c file, it’s just here there are 2 functions instead of 1. Do not swap these two functions places, do not place anything between them, do not rename them. + +WARNING: First, *setup()*. I have been told that the order of functions inside setup() seems somewhat random, that you can change the order and nothig would happen. It was just one opinion, but I must address it, since this misconception may cause big problems. The placement of every single function inside setup() was achieved through hours of testing and debugging, so the order is in fact very much intentional and strict. Misplacing them may cause a whole variety of bugs. + +* watchdog_init() shall be the first as it makes sure the whole program runs or resets after fail; +* display_init() has to be called before serial_init(). Otherwise the SPI initialisation inside display_init() will interfere with the start of the serial communication and some debugging messages in the beginning of the program run will not be shown correctly in the Serial monitor; +* generally, in projects, the serial communication shall be established as soon in the runtime as possible, to show even the earliest runtime bugs. This is why some people would expect serial_init() to be called as the very first. Yet, watchdog_init() has a priority because not only watchdog detects problems, but also can solve them by restarting the device. The serial_init() function itself is not imune to problems at all. Then there is display_init() which is explained in the previous bullet; +* file_sys_init() does not have to be exactly in this place, but it surely should be called after serial_init() since it may output some Serial debugging messages. Also, file_sys_init() shall be called before power_down_recovery() since this function may initiate the process of lost variables recovery from the file system – and for that, as you may imagine, it needs the file system to be already initialised; +* buttons_init() placing is not strictly dependant on other functions, but still has some logic to it. From one side, it is nice to give the user control over the device as soon as possible, hence the function gets called relatively early in the runtime. From the other side, this function initialises Interrupt Service Routines (meaning that pushing the buttons interrupts the normal program execution), that is why it was decided to allow all the communication ports and the file system to initialise before ISRs do – without interrupts. I am not saying that an interrupt happening during e.g. the file system initialisation may necessarily cause fails, but there is a chance. And since ISR bugs are also tricky to debug, it is better to simply avoid this risks no matter how small they are. +* battery_init() has a somewhat similar logic to it: it has to be called before battery_check() for obvious reasons, but other than that it could be moved around. Inside of it, battery_init() calls for ADC initialization. It has been rumoured that in certain cases the actual ADC hardware initialization, which happens behind the scenes, may take longer than the initialization function execution, which in turn may ruine ADC measurements made exactly after ADC initialization call. This claim was not tested in this particular project. But, just to be safe, it was decided to separate battery_init() (which makes the ADC initialization call) and battery_check() (which makes ADC measurements) with another function – in this case power_down_recovery() – in order to create an artificial time delay; +* power_down_recovery() can detect and handle brown-outs. Since brown-outs limit the program runtime to a split of a second, power_down_recovery() has to be called as early as possible. On the other hand, in reality, a brown-out would be an extremely rare occurance. In the software, there are other mechanics implemented to prevent the battery from discharging down to the level where brown-outs may start happening. +In any case, power_down_recovery() shall be called before battery_check(), because if there is a brown-out, it is way too late to measure the battery charge. Also, since power_down_recovery() triggers recovery of lost variables from the file system after device hard reset, it shall be called after file_sys_init(); +* battery_check() can put the device into extensive sleep if the battery charge is too low. This is why this function should be called before any serious work, like connecting to Wi-Fi and checking the Telegram chat, starts being executed. On the other hand, it should not be put before power_down_recovery() – explained in the previous bullet; +* telegram_check() uses Wi-Fi connection, which is power-demanding – this is why the function shall be called after battery_check(), so we already know that the device has enough battery charge for a wireless connection. telegram_check() works with the file system, so it shall be called after file_sys_init(), too. telegram_check() can change the state of the OTA flag (essentially, activation or deactivating OTA), so the function shall be called before ota_init(); +* ota_init() has to be as close to ota_waiting_loop() as possible and it has to be inside setup(). The position of this function is so optimal, that I would not consider moving it under any circumstances. +Now, about *loop()*. + +In a typical Arduino program, execution starts when the device is powered on. First, the code inside setup() runs once, and then the code inside loop() runs repeatedly, over and over, until the device is powered off. In other words, in most Arduino projects, the program cycles endlessly inside loop(). + +In this project, things work differently. The device uses a power-saving mode called Deep Sleep, which allows it to turn itself off and back on automatically. Each time the device wakes up, it starts fresh: first running setup(), then running loop() once, and finally going back to sleep again. This means the program cycle includes both setup() and loop(), instead of looping only inside loop(). + +It is important to note that the name loop() is required by the Arduino framework. Even though in this project loop() executes only once per cycle, the function must still be called loop(). + +Inside loop() you may find ota_waiting_loop() and pathfinder(). ota_waiting_loop() has to be as close to ota_init() as possible while still remaining inside loop(). I do not have a better explanation for this arrangement other than this is how it is required to be by the ArduinoOTA library. Since pathfinder() also puts the device into Deep Sleep, it shall be the last function to be called. Anything put after pathfinder() will not ever be executed. + +Finally, *pathfinder()*. It uses rtc_g.exam_status boolian variable as a flag to decide if to run exam mode that program cycle or not. Cluster number mode gets to run always since it is the default mode. Exam mode fully handles all that related to an exam including calculating how long to sleep for to wake up from Deep Sleep exactly after the exam. If the Exam mode runs, it will put the device to sleep itself, meaning that the program cycle would end in the Exam mode. There are a few points in the program where a program cycle ends and the device goes into Deep Sleep – here, in pathfinder() it is one of them. + +== 42-smart-cluster-sign.h file + +IMPORTANT: One thing in the file that stands out is the "ota.h" inclusion command being at the very bottom. It is not a mistake. It is the result of a workaround that allowed to use the basic Arduino Over-The-Air functionality with Deep Sleep. There might be a separate chapter on this workaround in this documentation. But here and now, it is important to say only that if the inclusion command was moved elsewhere, OTA functionality would not work any more. + +== battery_management.cpp file + +— + +== bitmap_library.h file + +Every unsigned char array is an image. In the head of the file you may see the table with the images information. Location represents the number of the row the image array starts from. + +== buttons_handling.cpp file + +The pins D3 and D9 are the only two pins which are not physically connected to anything, in other words they are floating. Untreated floating pins may cause hardware related bugs. Usually, such pins would be physically connected to the ground with resistors. Luckly, ESP32 has internal resistors that can be programmed to connect the pins to the ground with one line of code per pin. This is why in the buttons_init() function these two particular pins are set to be pulled down to the ground. Like this, these pins are much less likely to cause any troubles. If at some point you decide to use one of the pins, you absolutely can do so, just reprogram them the way you need it. + +== cluster_number_mode.cpp + +— + +== config.h + +— + +== constants.h + +— + +== credentials-example.h + +— + +== display_handling.cpp + +The display_cluster_number() is a big and complicated state machine. It is one of the paramount functions in the project. In order to understand how the project works, it is crucial to understand how this function works. + +The function manages states of the two parts of the cluster number display: the cluster number image on the left and the side notes area on the right. In order to achieve that, the function not only accepts input with information what to draw on the display, but also always remembers what is currently drawn on the display. + +The display_cluster flag is how the function remembers whether the cluster number image is on the display or not. The displaying_now variable is how the function remembers what is currently drawn in the side notes area. Both variables are static and employ the RTC_DATA_ATTR attribute – that allows them to keep their data even over Deep Sleep. + +At the very top, the function decides if anything needs to be drawn at all. It is possible that the function is asked to draw something that is already drawn on the display. In such case, drawing will be simply skipped. + +Then, for the cluster number image, the function checks if the image is currently on the display. If not, the image gets drawn and the event gets remembered. If the cluster number image was not on the display until now, then nothing is currently drawn on the side notes area either. So, with the next line of code, the function unblocks drawing of all of the side notes. + +Finally, the function decides what side note to draw and records what has been drawn into the displaying_now variable. + +The clear_display() function does not actually get used, as there always should be something drawn on the display. + +== exam_mode.cpp + +The exam_mode() function contains three time comparisons, that may be confusing at the first glance. We need them just to be sure that the device displays correct image at correct time even if the Exam mode was accidentally entered at an unusual time, e.g. mere minutes before the exam starts. + +Drawing on colored E-ink displays takes substential time (around 25 seconds), so if there is less that 10 minutes (600.000 milliseconds) before an exam, there is no point in showing the pre-exam warning sign just to replace it with the exam sign a couple of minutes later. Because when the people standing around see the display flashing for 25 seconds and then flashing for 25 seconds again after a couple of minutes, they will be thinking the Sign is broken. + +This is why we check if there is more than 10 minutes left till an exam. If so, the pre-exam warning sign gets drawn onto the display. If it is less than 10 minutes, the device simply waits till 25 seconds before the exam with whatever is already on the display. If it is even less than 25 seconds, it means that the device is already late and it needs to draw the exam sign as soon as it is physically possible. + +The call for an exam to start is in the very last line of the function. The call returns the exact time the device shall sleep for in order to wake up precisely at the end of the exam. + +== file_system.cpp + +— + +== globals.h + +The relations between the globals.h and globals.cpp files might not be completely obvious, so here is the description of what is going on there. Global objects and configuration structures are declared as extern in the header (globals.h) file to make them accessible across multiple translation units. Their definitions and initial values are provided in the corresponding globals.cpp file. + +== globals.cpp + +The relations between the globals.h and globals.cpp files might not be completely obvious, so here is the description of what is going on there. Global objects and configuration structures are declared as extern in the header (globals.h) file to make them accessible across multiple translation units. Their definitions and initial values are provided in the corresponding globals.cpp file. + +== intra_interaction.cpp + +In the request_exams_info() function, the time section in hours is hardcoded (currently, from 05:00:00 in the morning till 21:00:00 in the evening) in the request message to the server. It should be kept in mind that these hours refer to the Intra server time zone (which is Paris, France – GMT+1 in winter and GMT+2 in summer), not to the time zone the Sign is located in. This is not a problem if the Sign is in an European campus, but it would completely mess everything up for campuses in Asia and Australia. E.g. 05:00 in Paris is 13:00 in Seoul, South Korea and 21:00 in Paris is 4:00 in the morning of the next day for Seoul, South Korea. If you ever decide to refactor that part into an actual logic, do not forget that some countries do not switch between winter and summer time, but France surely does. + +== ota.h + +Despite containing function bodies instead of what you usually expect to find in a header file, ota.h has to be a header file format. In short, it is made this way, so this file can be included in the project from the main header file 42-Smart-Cluster-Sign.h, and so it is possible to use include guards to prevent the OTA code to be copied into every single cpp file in the project. + +In detail, because of the intricacies of how the OTA library works, the functions of the OTA functionality (the ones you may see in the ota.h file) must be placed right above the setup() function. That would have absolutely bloated the src.ino file, so it was decided to alocate the functions of the OTA functionality into a separate file and include it from the src.ino file. Unfortunately, despite multiple tries it did not work this way, probably due to how Arduino IDE works. Through trial and error the solution you see now in the project was found. It is not the most elegant one, but it checks the main boxes: the functions of the OTA functionality are located in a separate file for readability, during compilation those functions get included right above the setup() function, the solution does not cause compilation issues, OTA functionality actually works (when not blocked by a firewall). + +== power_down_recovery.cpp + +The functionality mostly relies on the ESP-IDF API functions. More on that exact functionality here: link:https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/system/misc_system_api.html[https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/system/misc_system_api.html#_CPPv418esp_reset_reason_t] — under esp_reset_reason() and Enumerations. + +== telegram_bot.cpp + +You might see the following call _bot.sendMessage(String(rtc_g.chat_id), message, "")_ throughout the file a lot, less elsewhere in the project files. That may make you think why not refactor the _rtc_g.chat_id_ variable into a _String_ type and remove all of those explicit casts into String objects. + +WARNING: Do not do that! The _rtc_g.chat_id_ variable is stored in the RTC memory that has got extremely limited space. String objects take simply too much space to be stored there. RTC memory overflow will inevitably cause a wide range of issues throughout the whole project! + +== telegram_compose_message.cpp + +— + +== time_utilities.cpp + +— + +== utils.cpp + +— + +== watchdog.cpp + +All the functions here are wraps around ESP-IDF watchdog API with added debugging messages and improved readability. More on ESP-IDF watchdog here under “Task Watchdog Timer (TWDT)” and “API Reference”: link:https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/system/wdts.html[https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/system/wdts.html] diff --git a/docs/tech_documentation/14-intra-api.adoc b/docs/tech_documentation/14-intra-api.adoc new file mode 100644 index 0000000..0e53e47 --- /dev/null +++ b/docs/tech_documentation/14-intra-api.adoc @@ -0,0 +1,125 @@ += How to Get Exams Info from Intra +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +The Smart Sign does it in the following 6 steps: + +. connects to Wi-Fi, +. connects to the 42 Intra server, +. asks the server for a temporary access token using the UID and the Secret, +. retreives the temporary access token from the server response, +. asks the server for exam information for a particular campus, a particular cluster, on a particalar date, using the obtained temporary access token, +. retreives the exam information from the server response. + +For testing purposes, this process can be recreated on a computer, in Terminal using Curl: + +. enter these variables into the Terminal ++ +[source,bash] +---- +CLIENT_ID=put_your_42_API_app_UID_number_here +SECRET_ID=put_your_42_API_app_Secret_token_here +---- + +. ask the 42 Intra server for a temporary access token ++ +[source,bash] +---- +curl -X POST --data "grant_type=client_credentials&client_id=${CLIENT_ID}&client_secret=${SECRET_ID}" https://api.intra.42.fr/oauth/token +---- + +. copy the access token from the server response and enter it as a variable into the Terminal ++ +[source,bash] +---- +TKN=put_received_access_token_here +---- + +. ask the server to send you the information about exams in the cluster C3 and put it into a .json file. 56 is the ID of the 42 Prague campus. Curl does not like square brackets [ ] in its calls, so they need to be escaped with a backslash \. ++ +[source,bash] +---- +curl -H "Authorization: Bearer $TKN" "https://api.intra.42.fr/v2/campus/56/exams&filter\[location\]=C3" > c3_exams.json +---- + +If you want to filter the results down to an exact date, as the Smart Sign does, use the following call instead. + +[source,bash] +---- +curl -H "Authorization: Bearer $TKN" "https://api.intra.42.fr/v2/campus/56/exams?filter\[location\]=C3&range\[begin_at\]=2024-07-12T05:00:00.000Z,2024-07-12T22:00:00.000Z" > c3_exams1.json +---- + +. this command opens the .json file in the Terminal ++ +[source,bash] +---- +python -m json.tool < prague_exams.json | grep "begin_at" | tr -d " ," | awk -F '"begin_at":' '{print("["++count"]:", $2)}' +---- + +== Example of the 42 server Temporary access token response as the smart sign sees it + +[source,http] +---- +HTTP/2 200 +date: Thu, 11 Jul 2024 13:19:37 GMT +content-type: application/json; charset=utf-8 +cache-control: no-store +etag: W/"77a2df7a4e20f5f76e6364d36bc76e8a" +pragma: no-cache +set-cookie: _mkra_stck=15e20a8020c702e70007eb1e185a06fb%3A1720703982.2018037; path=/; max-age=10; expires=Thu, 11 Jul 2024 13:19:47 -0000; HttpOnly +status: 200 OK +vary: Origin,Accept-Encoding +x-rack-cors: preflight-hit; no-origin +x-request-id: 3d153728-82b5-48a0-84e7-7c1f1efe598a +x-runtime: 0.076367 +cf-cache-status: DYNAMIC +report-to: {"endpoints":[{"url":"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=5%2Bb21KLqrzLETXPtKW2gerMAMrEPjiLAWT6eRUKeyuOVy3b5pvEr6Tc7D%2BMB%2BB4gqUHrTyXWaYy01CmZjQqUGReP7COyDKfBhKpl75Kwd%2FWrMWCVZD%2FkWhvM1iHF0V43hw%3D%3D"}],"group":"cf-nel","max_age":604800} +nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800} +server: cloudflare +cf-ray: 8a191610cec6bc03-FRA + + +{"access_token":"03e4cb9b861dad6c49f2267cf97bd18a942507efa7840dc971008d264596cf89","token_type":"bearer","expires_in":6564,"scope":"public","created_at":1720703340,"secret_valid_until":1722585613} +---- + +== Example of the 42 server exam information response as the smart sign sees it + +[source,http] +---- +HTTP/1.1 200 OK +Date: Thu, 28 Nov 2024 06:10:13 GMT +Content-Type: application/json; charset=utf-8 +Transfer-Encoding: chunked +Connection: close +Cache-Control: max-age=0, private, must-revalidate +etag: W/"4dc462b36f78c9e055076113bae0d605" +status: 200 OK +vary: Origin,Accept-Encoding +x-application-id: 67990 +x-application-name: 42PRAGUEAPI SCREEN +x-application-roles: None +x-content-type-options: nosniff +x-fast: false +x-frame-options: SAMEORIGIN +x-hourly-ratelimit-limit: 1200 +x-hourly-ratelimit-remaining: 1199 +x-page: 1 +x-per-page: 30 +x-rack-cors: preflight-hit; no-origin +x-request-id: 1d409b60-b763-4e10-af5a-8fe35593b80d +x-runtime: 0.196901 +x-secondly-ratelimit-limit: 2 +x-secondly-ratelimit-remaining: 1 +x-total: 1 +x-xss-protection: 1; mode=block +cf-cache-status: DYNAMIC +Server: cloudflare +CF-RAY: 8e9831883e36b353-PRG +9fa + + +[{"id":21213,"ip_range":"10.11.0.0/16,10.12.0.0/16,10.13.0.0/16","begin_at":"2024-11-28T14:00:00.000Z","end_at":"2024-11-28T17:00:00.000Z","location":"C3","max_people":25,"nbr_subscribers":4,"name":"EXAM STUD","created_at":"2024-11-22T08:20:02.386Z","updated_at":"2024-11-27T23:40:57.806Z","campus":{"id":56,"name":"Prague","time_zone":"Europe/Prague","language":{"id":2,"name":"English","identifier":"en","created_at":"2015-04-14T16:07:38.122Z","updated_at":"2024-11-18T11:22:47.733Z"},"users_count":1335,"vogsphere_id":52,"country":"Czech Republic","address":"AFI CITY TOWER Kolbenova 1021/9 Praha 9 - Vysočany","zip":"19000","city":"Prague","website":"https://42prague.com","facebook":"https://www.facebook.com/42Prague","twitter":"","active":true,"public":true,"email_extension":"42prague.com","default_hidden_phone":false},"cursus":[{"id":21,"created_at":"2019-07-29T08:45:17.896Z","name":"42cursus","slug":"42cursus","kind":"main"},{"id":21,"created_at":"2019-07-29T08:45:17.896Z","name":"42cursus","slug":"42cursus","kind":"main"},{"id":21,"created_at":"2019-07-29T08:45:17.896Z","name":"42cursus","slug":"42cursus","kind":"main"},{"id":21,"created_at":"2019-07-29T08:45:17.896Z","name":"42cursus","slug":"42cursus","kind":"main"},{"id":21,"created_at":"2019-07-29T08:45:17.896Z","name":"42cursus","slug":"42cursus","kind":"main"}],"projects":[{"id":1320,"name":"Exam Rank 02","slug":"exam-rank-02","difficulty":0,"parent":null,"children":[],"attachments":[],"created_at":"2019-07-29T09:05:05.890Z","updated_at":"2024-11-25T08:53:45.680Z","exam":true,"git_id":null,"repository":null},{"id":1321,"name":"Exam Rank 03","slug":"exam-rank-03","difficulty":0,"parent":null,"children":[],"attachments":[],"created_at":"2019-07-29T09:05:15.263Z","updated_at":"2024-11-25T08:56:10.466Z","exam":true,"git_id":null,"repository":null},{"id":1322,"name":"Exam Rank 04","slug":"exam-rank-04","difficulty":0,"parent":null,"children":[],"attachments":[],"created_at":"2019-07-29T09:05:24.256Z","updated_at":"2024-11-25T08:56:32.456Z","exam":true,"git_id":null,"repository":null},{"id":1323,"name":"Exam Rank 05","slug":"exam-rank-05","difficulty":0,"parent":null,"children":[],"attachments":[],"created_at":"2019-07-29T09:05:32.360Z","updated_at":"2024-11-25T08:56:53.071Z","exam":true,"git_id":null,"repository":null},{"id":1324,"name":"Exam Rank 06","slug":"exam-rank-06","difficulty":0,"parent":null,"children":[],"attachments":[],"created_at":"2019-07-29T09:05:39.838Z","updated_at":"2024-11-25T08:57:29.269Z","exam":true,"git_id":null,"repository":null}]}] +---- diff --git a/docs/tech_documentation/15-exam-simulation.adoc b/docs/tech_documentation/15-exam-simulation.adoc new file mode 100644 index 0000000..302072b --- /dev/null +++ b/docs/tech_documentation/15-exam-simulation.adoc @@ -0,0 +1,50 @@ += Exam Simulation and How to Use It +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +The project code contains a so-called Exam Simulation logic, so it is possible to test how the device behaves during exams without the necessity to wait for an actual exam. The Exam Simulation injects fictitious information about an exam (by default, scheduled for today from 18:00 till 21:00, 4 students attending) into the message from the Intra server, which causes the Sign to believe that there is an actual exam that day. Here is how to do it. + +. In the project files go to config.h and uncomment the following line ++ +[source,c] +---- +# define EXAM_SIMULATION +---- + +. Now, go to the utils.cpp file and scroll all the way down. There you will find the exam_simulation() function. + +. In the function, manually change the fictitious exam beginning and ending time the way you want it. + +When changing time, mind the time zone difference between your campus location and the Intra server location. The exam beginning and ending time must be stated in the time of the Intra server location. Let’s demonstrate it on an example: + +- Your are testing a Sign for the 42 SEOUL campus, located in Seoul, South Korea; + +- By googling, you discover that Seoul, South Korea has a time zone UTC+9. Also, South Korea does not switch between summer and winter time; + +- By this moment, you already know that the Intra server is located in France. France DOES switch between summer and winter time and you should account for that. In winter, France uses UTC+1 time zone, and in summer France uses UTC+2 time zone. When exactly this change happens may be looked up online. Let’s assume that at the time of this example the summer time applies. So, we will use UTC+2 for the Intra server; + +- Simple math operation shows that the time difference between the 42 SEOUL campus and the Intra server location is (9 – 2 =) 7 hours. It means, that at the time of this example, the Intra server clock is 7 hours behind the 42 SEOUL campus clock; + +- Let’s assume that you decided to test the Sign for a simulated exam starting at 14:00 (Seoul time) and ending at 15:30 (again, Seoul time). Since we already know that the Intra server time is 7 hours behind the Seoul time, we need to deduct these 7 hours from the time of our simulated exam: + +14:00 - 7 = 7:00 and 15:30 - 7 = 8:30 + +- So, now you know that to simulate an exam starting at 14:00 and ending at 15:30 Seoul time, in the exam_simulation() function you need to state 07:00 as the exam begin time and to state 08:30 as the exam end time. + +Of course, you need to go through this kind of analysis only for the first time you decide to use the Exam Simulation — next time you will already know how many hours to adjust for. For the Seoul campus it is slightly more complicated, since South Korea does not switch between summer and winter time but France does, the time difference will be constanty shifting there and back, and the Sign developer from the South Korea will always have to keep track of that. But if you are from a country that, like France, switches between summer and winter time, you do not even have to worry about that, as the switch happens roughly at the same time worldwide. + +. Compile the project and flash the device. Observe the device behaviour. If you need to do more tests, repeat the process. + +The flashing process is well described in the article link:07-development-environment.adoc[Getting ready to maintain and develop the project] earlier in this document, under “Uploading the changes”. + +. After you are finished testing, go to config.h and comment out the following line ++ +[source,c] +---- +//# define EXAM_SIMULATION +---- + +. Compile the project and flash the device one last time — now the Exam Simulation is off. Do not skip this step, as without it the device will keep showing your fictitious exam every single day. diff --git a/docs/tech_documentation/16-create-your-own-graphics.adoc b/docs/tech_documentation/16-create-your-own-graphics.adoc new file mode 100644 index 0000000..a1dab4c --- /dev/null +++ b/docs/tech_documentation/16-create-your-own-graphics.adoc @@ -0,0 +1,68 @@ += Create Your Own Graphics +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +You can create your own graphics to be displayed on the Sign. It may look complicated at first, but once you do it at least once, it will all become easy. + +Why is it complicated? Well, you cannot just drop an image into the Sign’s memory and display it. First, you have to convert your image into a C code and then put that C code into the Sign’s memory. The C code that represents an image is nothing else but a simple unsigned char array. In this array, each element represents the colour of each pixel in a bit format. Since there are a lot of pixels in an 800 x 480 image, the array will be very big, yet it will be just a simple array. Usually, they call this arrays bitmaps. If you would like to learn more, „bitmaps“ is the term you should google. + +So, here is the simple instruction: + +*get an image* > *convert into bitmap* > *put the bitmap into the project code* > *upload to the Sign*. + +Here are the steps in details: + +. *Create your image* + +- it has to be 800 x 480 pixels or smaller, + +- JPEG/JPG format only, + +- it can be only Black-and-White or Red-Black-and-White. + +. *Convert your image into a bitmap*. To do that, you may use the tool located in this repository: + +tools > epd_image_converter + +- download the tool from GitHub onto your local machine, + +- put all the images you want to convert into the epd_image_converter folder, + +- open the epd_image_converter folder in the Terminal, + +- use the following command to create Black-and-White image bitmap: + +`./epd_image --BW --DITHER Your_Image_Name.jpg Any_Name.h` + +- use the following command to create Red-Black-and-White image bitmap: + +`./epd_image --BWR --DITHER Your_Image_Name.jpg Any_Name.h` + +- Your_Image_Name.jpg is the image you want to convert into a bitmap, + +- Any_Name.h is the file where the bitmap array will be generated, + +- Black-and-White images get converted into 1 bitmap, + +- Red-Black-and-White images get converted into 2 bitmaps: Red-White and Black-White. + +. *Put your image bitmap into the project code* + +- open all the Any_Name.h files that you generated in the previous step, + +- give the arrays apropriate names, + +- open the project code files and find the bitmap_library.h file, + +- copy-paste the arrays from the the Any_Name.h files into the bitmap_library.h file of the project. + +. *Use your bitmap to be displayed on the Sign* + +- go to the display_handling.cpp file of the project and find the apropriate function to display your image, + +- implement when and how your image shall be displayed. + +. *Upload the new software to the Sign and watch it run.* diff --git a/docs/tech_documentation/17-how-to-draw-on-the-display.adoc b/docs/tech_documentation/17-how-to-draw-on-the-display.adoc new file mode 100644 index 0000000..8f85b46 --- /dev/null +++ b/docs/tech_documentation/17-how-to-draw-on-the-display.adoc @@ -0,0 +1,56 @@ += How to Draw on the Display +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +COORDINATES, SIZES IN PIXELS AND ROTATION + +In the *setRotation(uint8)* function you may set how the window you want to output on the display will be turned compared to the display physical orientation. All the displays have their default physical orientation, but it may vary from display to display, so finding out the display's default physical orientation is the starting point for any work with display graphics. It is easy to do: take a look at the display's physical appearence, note the side where a ribbon of wires sticks out of it — that's the bottom side of this display's default physical orientation. Knowing that, we now can set the rotation: + +setRotation(0) — for the default rotation, it is equal to the display default physical orientation + +setRotation(1) — will rotate the window 90 degrees clockwise comparing to the display default physical orientation + +setRotation(2) — will rotate the window 180 degrees (or simply flips it up-side-down) comparing to the display default physical orientation + +setRotation(3) — will rotate the window 270 degrees clockwise comparing to the display default physical orientation + +Keep in mind that however you rotate the window, its system of coordinates always rotates with it. So, the 0,0 coordinates are always in the TOP LEFT corner of your window, but not necessarily in the top left corner of your display. + +When dealing with the Partial Update windows, it is important to remember about one unobvious limitation of the e-paper controller: partial update window size and position are on byte boundary with physical x direction. Meaning that the controller may set the horisontal window boundaries only on every 8th pixel comparing to its default physical orientation. Let's summ it up with a simple rule that is easy to follow: + +The value of x and the value of width should be multiple of 8, for rotation 0 or 2, + +the value of y and the value of hight should be multiple of 8, for rotation 1 or 3. + +The function *setPartialWindow(x, y, width, height)* allows us to create a window of any size and, instead of the whole display, update only the content of this window. It is very useful feature since it is much faster than the conventional full display update with the setFullWindow() function. Moreover, this type of an update does not flicker. So, if you need to get rid off of the flickers between the slides, with setPartialWindow(x, y, width, height) you may set the whole display as a Partial Update window. But be careful as THE FIRST UPDATE AFTER TURNING ON OR RESET SHALL ALWAYS BE THE FULL DISPLAY UPDATE with the setFullWindow() function. Otherwise the display may be irrevertably stuck on the last image for ever. + +It may be useful to note the relationship between the setPartialWindow(x, y, width, height) and the setRotation(uint8) functions. Some may think that setRotation(uint8) may turn a Partial Update window created with the setPartialWindow(x, y, width, height) function. And it is true, but only to some extent. setRotation(uint8) does not turn Partial Update windows, but it actually turns the whole display window orientation with Partial Update windows inside of it. That is why when we change the display orientation with the setRotation(uint8) function, we also need to remember that the system of coordinates for x and y in the setPartialWindow(x, y, width, height) also changes. And when the system of coordinates changes, the understanding of width and height changes with it. E.g. if while setRotation(0) the window's width is 800 and the window's height is 480, then while setRotation(1) the window's width becomes 480 and the window's height becomes 800. + +For some unexplainable reason when it comes to the text body coordinates, their logic is very different from windows and images. The x and y coordinates point to the bottom left corner of the first line of text even if there are multiple lines of text. In comparison, the common logic for the windows and bitmaps coordinates are that their x and y point to the top left corner of the image. So, if you used display.setCursor(x, y) command to output the word HELLO onto the display, the x and the y there would point to the very bottom left pixel of the letter H. + +The coordinates logic is also tricky when it comes to the command + +*display.getTextBounds(output, 0, 0, &text_box_x, &text_box_y, &text_width, &text_height)* + +This command can view any text as the smallest box that can accomodate the given text and give you all the needed information to create such a box on the screen. It is very useful when you want to update a piece of text or a single character on your display instead of the whole display. The function takes a few parameters: the text in the form of a String varible (here it is "output"), x and y coordinates of the area you want to place the text (here they are "0, 0" - the top left corner of the display), pointers to x and y coordinates (this is how you will get x and y coordinates for the top left corner of the box for the text), pointers to the width and height (this is how you will get width and height values of the box for the text). It is not required to change the "0, 0" coordinates because this function does not place anything anywhere - it uses this values only for calculations. Though, it is important to remember, that when you use the default "0, 0" coordinates, the value in the text_box_y variable will be NEGATIVE. Why does this happen? Exactly because of the difference in the coordinates logic between texts and windows/bitmaps/boxes. If you remember, x and y for texts point to the BOTTOM left pixel of the text; but x and y for windows/bitmaps/boxes point to their TOP left pixel. The display.getTextBounds tries to compensate this logic difference. E.g. if you were to place the word HELLO with the 10 size font into the top left corner (0, 0) of the display, you would need to call setCursor(0, 10); but if you were to creare a partial update window in the the top left corner (0, 0) of the display to update that text, you would need to call setPartialWindow(0, 0, text_width, 10). + +But noone has time to deal with different coordinates logic! We want to give the function the text and just one set of x,y coordinates and the text shall simply aline with my partial update window automatically! That's where the getTextBounds may help. In our example our target display coordinates are x = 0, y = 0 (the top left corner), so let's place HELLO in there with a partial display update: + +display.setTextSize(10); + +display.getTextBounds("HELLO", 0, 0, &text_box_x, &text_box_y, &text_width, &text_height); + +// now text_box_x has value 0, text_box_y == -10, text_width == HELLO-width value (I don't know it), text_height == 10 + +display.setPartialWindow(x, y, text_width, text_height); + +// that creates a window in the top left corner of the display, HELLO-width pixels wide and 10 pixels high + +display.setCursor(x - text_box_x, y - text_box_y); + +// this is how we compensate for the text coordinates logic: for x (0 - 0 = 0) and for y (0 - (-10) = 10) coordinates + +display.print("HELLO"); diff --git a/docs/tech_documentation/18-time-keeping.adoc b/docs/tech_documentation/18-time-keeping.adoc new file mode 100644 index 0000000..fdbae10 --- /dev/null +++ b/docs/tech_documentation/18-time-keeping.adoc @@ -0,0 +1,28 @@ += The Intricacies of Time Keeping +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +The project has one source of time-related data – the Intra server. It provides both the actual time for the system and the exam time. + +Actual time data gets updated every time the device checks the exam by calling the fetch_exams() function. The actual time data gets stored in 5 global variables: com_g.hour, com_g.minute, com_g.day, com_g.month, com_g.year. The values in those global variables are already adjusted to the time zone of the Sign and to the summer/winter time. + +Exam time from the Intra server gets updated when the fetch_exams() function is called. The exam time is then stored in the following 4 global variables: rtc_g.exam_start_hour, rtc_g.exam_start_minutes, com_g.exam_end_hour, com_g.exam_end_minutes. There is no exam date variable, because the exam time is searched only for the current day. The values in those global variables are already adjusted to the time zone of the Sign and to the summer/winter time. + +It is worth noting that all of the time-related data does not simply come nicely adjusted for this particular device to use. All the adjustments happen on the device. To understand how it works, it is important to have information about the sources of the raw data. + +The Intra server provides only standart UTC time. It never adjusts for Summer or Winter time. It does not care about time zones. All of that – both summer/winter time change and time zone adjustment – is handled on the device by the get_and_ensure_current_time() function and its helper functions: winter_summer_time_offset(), last_sunday() and is_weekday(). + +It is important to mention that the winter_summer_time_offset() function calculates when to switch between summer and winter time based on the pattern of the European Union DST system. *_It may not be suitable for the countries outside of the EU!_* The pattern that the function obeys is the following: + +* last Sunday of March — switch to summer time, +* last Sunday of October — switch back to winter time. +Located in the config.h file, there is a very important time-related macro TIME_ZONE. This macro is the place where you have to manually write the time zone of the campus where you use the Sign. + +Be aware, that if your country switches between winter/summer time (DST system), it means that your country basically has two different time zones – one in winter and a different one in summer. Winter time zone is universally considered as the default one. This is why the winter time zone is what you need to write in the TIME_ZONE macro. + +E.g.: in winter, France uses Central European Time and has the UTC+1 time zone; but in summer France uses Central European Summer Time and has the UTC+2 time zone. Winter time zone is the default one, this is why for any French campus TIME_ZONE is 1. + +Include "-" sign if it applies to the time zone of your campus. Do not include "+" sign at all. diff --git a/docs/tech_documentation/19-service-messages.adoc b/docs/tech_documentation/19-service-messages.adoc new file mode 100644 index 0000000..4a3f5de --- /dev/null +++ b/docs/tech_documentation/19-service-messages.adoc @@ -0,0 +1,162 @@ += Service Messages Meaning +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +During its operation, there are some messages that the Sign may output. Some of those messages appear on the display as well as in the Telegram chat, but some appear only in the chat. The messages are designed to be naturally understandable on their own. In this article you may find all such messages, when they occure, and additional explanation to their meaning. + +You may notice that for quite a handful of issues the Sign simply says that it could not check the exam time and suggests students to check whether the classroom is reserved themselves. However, it is often possible to find out exactly what happened from the message the Sign sends to the Telegram chat or from the debugging output the Sign transmits into the Serial Monitor (if the DEBUG macro was not deactivated). + +[cols="1,1,1",options="header"] +|=== +|What You See on the Display +|What You Receive in the Telegram Chat +|What to Do about It + +a| +image::low-battery-note.jpeg[low battery note] +|Dear User, I need your assistance! My battery is low. I am currently sitting on 3% and it keeps on draining! Please, charge me as soon as possible. +a| +You need to charge the Sign as soon as possible. It may probably run for 3-4 more days, but then it will become non-operational. +Everything about charging the Sign can be found in the article link:11-hardware-maintenance.adoc[Hardware maintenance] here in the document. + +a| +image::battery-empty.jpeg[battery empty] +|Dear User, I need your assistance! My battery is dead, so I am stopping all the processes and turning off. Please, charge me and I will turn back on again. +a| +At this moment the Sign is completely off. It will not work again untill you charge its battery. +Everything about charging the Sign can be found in the article link:11-hardware-maintenance.adoc[Hardware maintenance] here in the document. + +a| +image::intra-token-expires.png[intra token expires] +a| +Dear User, I need your assistance! My SECRET token expires in X days / expires tomorrow! / has already expired!!! And without it I would not be able to do my job. Please, retreive a new SECRET token from the Intra API page and send it to me in this chat. Your message should start with a forward slash (/), so I could recognise it. + +Here is an example: +/s-xxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +After it is done, I will leave you in pease for another month =) +a| +The message is pretty descriptive. + +If you did not build the Sign yourself, your Bocal IT team may have a special Secret token for the Sign, so it would be a wise first step to go and ask them. + +If you did, get a new Secret token the way you did it the first time. Here are the instructions: https://api.intra.42.fr/apidoc/guides/getting_started + +a| +image::could-not-check-exams.png[could not check exams] +a| +Dear User, I am writing to report an exceptional situation! Code: GET_TIME 102 + +After several retries, I was unable to connect to Wi-Fi to get current time. If you see this message, the problem has been probably solved already. Unfortunatelly, because of this issue I am displaying an Intra error and I cannot inform students about exams. So, if there is one today, you probably should notify them yourself. + +I will try to restore my work in a few hours. If you do not receive this message again, it means that I was successful. +a| +There are some issues with the Wi-Fi connection. If you see the error message on the display, but there is not a single new message from the Sign in the Telegram chat at all, then that’s exactly because of this problem and it is still ongoing. If there IS the message you can see on the left in the Telegram chat, probably the issue has already been solved. + +Plausible causes: +- Internet is not accessible due to issues on the provider’s side or due to the unpaid bills; + +- Wi-Fi router issues: power outage, Firewall blockage, poor signal, change of the Wi-Fi network name and/or password; + +- the Sign issues: located too far away from the router, the battery is at the end of its lifespan and cannot supply enough power for Wi-Fi connection; + +- human factor: the name and/or the password of your Wi-Fi network you entered into the credentials.h file have a typo or some other kind of a mistake. + +a| +image::could-not-check-exams.png[could not check exams] +a| +Dear User, I am writing to report an exceptional situation! Code: GET_TIME 103 + +After several retries, I was unable to connect to the NTP server and I do not know what time it is now. The issue is on the server side. + +I understand that you can hardly do anything about it. I just wanted to let you know that now I cannot inform students about exams. So, if there is one today, you probably should notify them yourself. + +I will try to connect to the NTP server and to get time in a few hours. If you do not receive this message again, it means that I was successful. +a| +The NTP server is probably offline. + +An NTP server is where the Sign gets the actual current time and date from. Without this data the Sign cannot operate. + +When an NTP server fails, you cannot really do anything about it but to wait. If this happens too often (more than two occasions a year), it might be the time to change the NTP server. It’s in the get_time() function. + +Much less likely this was caused by a bug in the program. The chances of that are low, but never zero. + +a| +image::could-not-check-exams.png[could not check exams] +a| +Dear User, I am writing to report an exceptional situation! Code: INTRA 503 + +After several retries, I was unable to connect to the Intra server. Since you see this message, the problem is probably not with the Wi-Fi connection, but on the Intra server's side. Likely, you can hardly do anything but wait. I cannot inform students about exams, so if there is one today, you probably should notify them yourself. + +I will try to connect to Intra in a few hours. If you do not get this message again, it means that I was successful. +a| +This message indicates issues with Intra server, namely that the server does not respond to the calls of the Sign. + +If waiting it out did not help, try and test obtaining exams data manually on your computer as it is described in the link:14-intra-api.adoc[How to get exams info from Intra] article here in the document. If you had no issues doing so on your computer, reboot the Sign by pressing the R button on the back of the device. If nothing happens within 20 minutes, you may need to start debugging. A good place to start is connecting the Sign to a computer and checking what it says in the Serial Monitor. Another place to look at is the intra_connect() function. + +a| +image::could-not-check-exams.png[could not check exams] +a| +Dear User, I am writing to report an exceptional situation! Code: INTRA 401 + +After several tries, I was unable to get the access token from the Intra server. From the experience, it has been probably caused by a faulty or expired SECRET token. Another possible reason is that they changed the Intra server response layout and I cannot see the access token, because now it is located somewhere else. + +Anyhow, now I cannot inform students about exams, so if there is one today, you probably should notify them yourself. I will try to get the access token in a few hours. If you do not get this message again, it means that I was successful. +| + +a| +image::could-not-check-exams.png[could not check exams] +a| +Dear User, I am writing to report an exceptional situation! Code: INTRA 404 + +After several tries, I was unable to get any exam information from the Intra server. Instead of dates and times of exams, I simply receive an empty page. If it is caused by a problem on my side, I am unaware of it. + +Anyhow, now I cannot inform students about exams, so if there is one today, you probably should notify them yourself. I will try to get the exam information in a few hours. If you do not get this message again, it means that I was successful. +| + +a| +image::ota-waiting-update.png[ota waiting update] +a| +_While waiting for the updating to start:_ + +OTA Update is active + +_When the updating has just started:_ + +Updating... +| + +a| +image::ota-update-canceled.png[OTA update was canceled] +|OTA Update port closed +| + +a| +image::ota-update-successful.png[ota update successful] +|Successfully updated! +| + +a| +image::ota-update-failed.png[ota update failed] +|Something went wrong. Updating was not completed. Try again later +| + +a| +image::telegram-bot-error.png[Telegram bot error] +a| +— + +_(It’s a Telegram error. You cannot expect to get a message in Telegram when there is a Telegram error)_ +a| +This service message means that the Sign does not have access to the Telegram chat because it does not have the chat ID. + +This issue may have occurred because the memory has got corrupted. In this case, the Secret token has probably been lost too. To fix this, go to the Telegram chat with the Sign that you already have and write any message to it starting with the ”/” character. When the Sign responds, send it the Secret token again just to be safe. +If you do not have a chat with the Sign, go to credentials.h, copy the Telegram bot name of the Sign from the BOT_NAME definition and search for it on Telegram. When you find the bot, just start a chat with it and the chat ID will get restored automatically. + +| +| +| +|=== diff --git a/docs/tech_documentation/20-libraries.adoc b/docs/tech_documentation/20-libraries.adoc new file mode 100644 index 0000000..cdcddc3 --- /dev/null +++ b/docs/tech_documentation/20-libraries.adoc @@ -0,0 +1,115 @@ += Libraries and Their Use +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +The project was built in Arduino IDE 1.8.19. + +It uses board 'esp32' version 3.0.7 + +The libraries in bold are explicitly included in the project. + +[cols="1,1,2",options="header"] +|=== +|Library +|Version +|Purpose + +|*Arduino.h* +| +|String variables manipulations + +|*LittleFS* +|2.0.0 +|stores data even without electricity (Telegram chat number, Secret, OTA flag value) + +|FS +|2.0.0 +|dependency for the LittleFS library + +|*ArduinoOTA* +|2.0.0 +|for the Over The Air update functionality + +|*WiFiUdp* +|2.0.0 +|dependency for the ArduinoOTA library + +|*ESPmDNS* +|2.0.0 +|dependency for the ArduinoOTA library + +|Update +|2.0.0 +|dependency for the ArduinoOTA library + +|*stdio.h* +| +|provides printf() function for the DEBUG macro + +|*stdint.h* +| +|provides fixed-width integer types + +|*esp_system.h* +| +|allows to use ESP-IDF native functions + +|*esp_sleep.h* +| +|allows to use the Deep Sleep power-saving functionality + +|*driver/adc.h* +| +|for battery charge measurements + +|*esp_task_wdt.h* +| +|program execution watchdog + +|*Wire* +|2.0.0 +|for SPI reconfiguration in the ft_display_init function + +|SPI +|2.0.0 +|dependency for the Wire library + +|*GxEPD2_3C* +|1.5.2 +|3-coloured version of the GxEPD2 library for e-paper displays + +|*GxEPD2_BW* +|1.5.2 +|dependency for the GxEPD2_3C library + +|Adafruit_GFX_Library +|1.11.8 +|dependency for the GxEPD2_3C library + +|Adafruit_BusIO +|1.14.4 +|dependency for the GxEPD2_3C library + +|*Fonts/FreeSansBold24pt7b.h* +| +|the fonts come from the Adafruit GFX library which gets called by the GxEPD2 library + +|*WiFi* +|2.0.0 +|for Wi-Fi functionality + +|*WiFiClientSecure* +|2.0.0 +|for secure HTTPS requests + +|*UniversalTelegramBot* +|1.3.0 +|Telegram bot; for wireless SECRET update and low battery notifications + +|ArduinoJson +|6.21.3 +|dependency for the UniversalTelegramBot library +|=== diff --git a/docs/tech_documentation/21-known-bugs-and-fixes.adoc b/docs/tech_documentation/21-known-bugs-and-fixes.adoc new file mode 100644 index 0000000..ce5dd2d --- /dev/null +++ b/docs/tech_documentation/21-known-bugs-and-fixes.adoc @@ -0,0 +1,81 @@ += Bugs and Suggestions How to Fix Them +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +*_Display does not work / does not draw an image._* + +* you may have miscalculated the image coordinates and the image gets drawn outside of the display field of coordinates. Remember that setRotation also rotates the display field of coordinates. Remember that coordinates always point to the top left corner of an image, but for a text it is the bottom left pixel of the first character in the first line. +* you may have misaligned the image with the partial update window if you are using one. Remember that even when you draw an image in a partial update window, you use the whole display field of coordinates to place it; a partial update window does not have its own field of coordinates. +* the display driver memory may be full. Run your program and open the Serial monitor. When an image gets drawn on the display, in the Serial monitor it says “Updating xxxxxxxxxx” where xxxxxxxxxxxx is a very long number that can be different each program run. Among all the outputted messages find the “Updating” message that corresponds to your image being drawn. Now look higher and find the “Power on” message. Keep looking higher and find the “Power off” message, it should not be separated from your “Updating” message by other “Updating” messages. If you cannot find it, it means that this is a bug. To fix it, in your code use the function to force the display to power off before drawing the image. The display will power back on automatically. +*_Flash memory became too small to accomodate the software._* + +* ESP32C3 comes with 4MB of onboard memory. This project‘s default memory partition scheme "Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)" provides exactly 1 966 080 Bytes for the software program, another 1 966 080 Bytes as an OTA buffer and 194 560 Bytes for the file system files storage space. +* Commenting out the *#pragma GCC optimize ("O3")* line in config.h will make the compiler optimize the code for lower software image size instead of faster program performance. That may win over about 2-3 KB of memory space. +* Before compiling the software image, in the „Tools“ menu of Arduino IDE, go to the Core Debug Level and switch it from „Verbose“ to „None“ — this simple step can free up to 3% of the memory space. +* Downgrade the project from the esp32 board version 3.0.7 to the esp32 board version 2.0.16. This step may require to refactor the watchdog and the battery management functionalities, but it may free up to 10% of the memory space. +* In the „Tools“ menu of Arduino IDE, go to the Partition Scheme and choose the option „Huge APP (3MB No OTA/1MB SPIFFS)“. Doing so will free up a whopping 30% of the memory space. Unfortunatelly, the Over-The-Air update functionality will not have a buffer for its work and so will no longer be available. At this point the OTA functionality code may well be removed from the project. +*_Some RTC variables loose thier values over the Deep Sleep._* + +* There could be more than one reason to it, but most likely it is caused by the RTC memory overflow. Try removing some less important variables from the RTC domain. The String Object type variables (or simply String variables) are known to have a big overhead and thus to take a lot of memory space. It is encouraged not to use them in the RTC domain at all and to use C-style char arrays instead. +*_DEBUG_PRINTF does not output a message._* + +* the DEBUG_PRINTF macro cannot output String type variables natively. To do that, you need to explicitly cast the String variable into the C-style string with c_str() command. Look for examples in the program code. +*_Serial monitor is empty / outputs gibberish._* + +* check the DEBUG macro in the config.h file. The DEBUG definition should not be commented out for the Serial output to work. Additionally, you can set the Core Debug Level to "Verbose" in the Arduino IDE Tools to get detailed information about the firmware processes. +* make sure that the baud rate in the Serial monitor is set to the same baud rate as in the config.h file. +* you may encounter such behaviour right after the software update. It is normal. Try closing and opening again the Serial monitor window. If that does not help, push the Reset ("R") button on the module. +*_Serial monitor skips some messages / does not show some messages._* + +* it is a common situation at the beginning of the program. Serial communication between the computer and the microcontroller needs time to stabilise and synchronise itself. ESP32-C3 USB Serial is especially prone to this issue. To overcome it, increase the delay inside of ft_serial_init() or add a few empty messages to be outputted after the Serial.begin() command. You may well try to implement both of the suggested solutions at the same time. +*_Wi-Fi does not connect / reconnect without apparent reason._* + +* thoroughly check your network SSID and password spelling. Surprisingly, it is a very widely spread cause. A single character written small instead of capital may easily prevent you from connecting. +* make sure not to use ft_delay() in any of your functions responsible for connecting or reconnecting to Wi-Fi. The ft_delay() function not only delays the program execution but also puts the microcontroller's inner Wi-Fi module to sleep. Using ft_delay() in functions responsible for retrieving information from the Internet may result in unexpected behaviour. If you are not sure that using ft_delay() is safe in your particular function, use delay() instead. +*_OTA does not work. Cannot see the device in the ports list._* + +* make sure that the Sign and your computer are connected to the same Wi-Fi network and to the same Wi-Fi modem within that network. In the Telegram chat prompt the Sign with the „/status“ command to see the MAC address of the Wi-Fi modem it is currently connected to. +* try closing and reopening Arduino IDE. +* the school firewall may be blocking OTA connection. Ask your campus system administrator if it could be overcome. +*_Adding multiple Strings together with the “+” command causes compilation error._* + +* strangely, sometimes the compiler may not like it in one part of the code and be completely fine with it in another. The solution is to explicitly cast the variable after the first “+” command into String with the String(your_variable_or_text) command. Understandably, it is strange to cast a String variable into String, but it works. +*_WARNING: Skipping SSL Verification. INSECURE!_* + +* not a bug. +* this message appears when connecting to the Intra server and is caused by the following line in the intra_interaction.cpp file: „client1.setInsecure();“. +* on one hand, it can be solved by getting and setting up a certificate for this connection. On the other hand, it does not affect the program run at all and can be ignored. +*_setSocketOption(): fail on 0, errno: 9, "Bad file number"_* + +* a minor issue and does not necessarily indicate a problem with the program. +* this message may appear when the Smart Sign fails the first attempt to get a server response from the Intra server and goes for the second or third attempt. + +* this error can occur when you try to set a socket option on a socket that has already been closed or is in the process of being closed. This can happen during the transition between closing the previous connection and opening a new one. As long as the SSL/TLS communication with the Intra server is functioning correctly after the reconnection, this error can generally be ignored. +*_spiAttachMISO(): SPI Does not have default pins on ESP32C3!_* + +* not a bug. +* This message appears when the microcontroller assigns pins for the display SPI port. In this project we do not use the MISO pin (thus the „-1“ value defined for the SPI_MISO_PIN in the constants.h file). +*_401 Unauthorized. Error! Server response came without the Access Token._* + +* often happens when something is wrong with the Secret token authentication, commonly with the Secret token itself. Most likely, an extra character was added to your Secret token somewhere along the way. The character may even not to be visible in the Serial monitor. It may happen when you write to or read from the filesystem files. Try using trim() on the variable (e.g. your_string_variable.trim();), it will remove spaces and/or new line signs at the beginning and at the end of the string. +* rarely may happen due to the Intra server maintenance. There is no solution to it but to wait. +*_Compilation error: “Section .dram0.bs 'Will Not Fit In Region Dram0_0_seg' Region.`Dram0_0_seg 'Overflowed by 9648 Bytes. Collect2: Error: LD Returned 1 Exit Status”_* + +* it means that the program takes more RAM space than it is available. DRAM stands for Data Random Access Memory and is used for data. +* This error may be caused for example by excessive use of global variables, large arrays, big buffers, etc. +* The most likely reason for this error in this project is the display buffer being too big. The ESP32 and the ESP32-S2 are especially prone to this issue. To overcome this problem the display buffer size should be reduced. It can be done in the display instantiation. +Here is the default display buffer instantiation: +GxEPD2_3C display(GxEPD2_750c_Z08(SPI_SS_PIN, DC_PIN, RST_PIN, BUSY_PIN)); +And here is a reduced display buffer instantiation: GxEPD2_3C display(GxEPD2_750c_Z08(SPI_SS_PIN, DC_PIN, RST_PIN, BUSY_PIN)); +* Less likely reason for this error is an excessive use of the global scope for data. To solve it reduce the number of global variables, use the file system to store the data instead of arrays. +*_Software update fails while trying to connect to the microcontroller board_* + +* Go into the "Tools" menu of the Arduino IDE and change the Upload Speed to 115200. Sometimes the IDE automatically sets the Upload Speed to the highest value and your board may happen not to support it. +*_[INTRA] Error! Server response to the Access Token request was not received._* + +*_[INTRA] Error! Server response to the Exam Time request was not received._* + +* If any of these error messages keep appearing in the Serial monitor, it is likely that it is not enough time for the Intra server to proceed the request from the Sign. Try going into the config.h file and incrementally increasing the value of the SERVER_WAIT_MS macro by 500 milliseconds each time. diff --git a/docs/tech_documentation/22-reporting-and-suggestions.adoc b/docs/tech_documentation/22-reporting-and-suggestions.adoc new file mode 100644 index 0000000..1fede34 --- /dev/null +++ b/docs/tech_documentation/22-reporting-and-suggestions.adoc @@ -0,0 +1,10 @@ += New Bugs and Future Development Suggestions +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +All newly discovered bugs should be documented in the "Issues" tab of the project's GitHub repository. This helps keep track of problems and facilitates community engagement in resolving them. + +Any suggestions for future development or enhancements can be added to the "Suggestions for Contributions" section in the README file of the project's GitHub repository. This allows everyone to see potential improvements and participate in developing them. diff --git a/docs/tech_documentation/23-confidential-information.adoc b/docs/tech_documentation/23-confidential-information.adoc new file mode 100644 index 0000000..b4e65d5 --- /dev/null +++ b/docs/tech_documentation/23-confidential-information.adoc @@ -0,0 +1,8 @@ += Suggestions for Dealing with Confidential Information +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +NOTE: This chapter is a stub — see issue #71. diff --git a/docs/tech_documentation/24-external-sources.adoc b/docs/tech_documentation/24-external-sources.adoc new file mode 100644 index 0000000..cca7e22 --- /dev/null +++ b/docs/tech_documentation/24-external-sources.adoc @@ -0,0 +1,56 @@ += External Information Sources +:imagesdir: media +ifndef::backend-pdf[] +:toc: auto +:sectnums: +endif::[] + +NTP (time) server API documentation + +link:https://cplusplus.com/reference/ctime/tm/[https://cplusplus.com/reference/ctime/tm/] + +How to Get Date and Time with the Time.h library — article + +link:https://randomnerdtutorials.com/esp32-date-time-ntp-client-server-arduino/[https://randomnerdtutorials.com/esp32-date-time-ntp-client-server-arduino/] + +GDEY075Z08 display data sheet + +link:https://www.laskakit.cz/user/related_files/gdey075z08.pdf[https://www.laskakit.cz/user/related_files/gdey075z08.pdf] + +GDEY075Z08 display code example + +link:https://github.com/LaskaKit/Testcode_examples/blob/main/Displays/E-Paper/7-50/GDEY075Z08_GxEPD2/GDEY075Z08_GxEPD2.ino[https://github.com/LaskaKit/Testcode_examples/blob/main/Displays/E-Paper/7-50/GDEY075Z08_GxEPD2/GDEY075Z08_GxEPD2.ino] + +UC8179 - the display hardware driver - data sheet link:https://www.laskakit.cz/user/related_files/uc8179.pdf[https://www.laskakit.cz/user/related_files/uc8179.pdf] + +GxEPD2 library - the display software driver - online page + +link:https://github.com/ZinggJM/GxEPD2[https://github.com/ZinggJM/GxEPD2] + +Online forum for GxEPD2 library troubleshooting discussions + +link:https://forum.arduino.cc/t/good-display-epaper-for-arduino/419657[https://forum.arduino.cc/t/good-display-epaper-for-arduino/419657] + +ESP32 RAM issue discussion page + +link:https://github.com/espressif/arduino-esp32/issues/1163[https://github.com/espressif/arduino-esp32/issues/1163] + +XIAO ESP32C3 development board instruction page + +link:https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/[https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/] + +Getting started with 42 API guide + +link:https://api.intra.42.fr/apidoc/guides/getting_started[https://api.intra.42.fr/apidoc/guides/getting_started] + +42 API Documentation + +link:https://api.intra.42.fr/apidoc[https://api.intra.42.fr/apidoc] + +Video tutorial about troubleshooting the ArduinoOTA library + +link:https://www.youtube.com/watch?v=z_btZfxrS48[https://www.youtube.com/watch?v=z_btZfxrS48] + +The best instruction ever on the buttons implementation with ESP32 + +link:https://esp32io.com/tutorials/esp32-button[https://esp32io.com/tutorials/esp32-button] diff --git a/docs/tech_documentation/README.md b/docs/tech_documentation/README.md new file mode 100644 index 0000000..8660c60 --- /dev/null +++ b/docs/tech_documentation/README.md @@ -0,0 +1,44 @@ +# Technical documentation + +Readable, searchable chapters for the 42 Smart Cluster Sign. This index replaces the old hand-typed table of contents. + +To build a single PDF of the whole book, run `make docs` from the repository root (requires `asciidoctor-pdf`). The PDF gets created in `build/Technical_Documentation.pdf` and is automatically gitignored. + +## Chapters + +1. [Vocabulary of Terms](01-vocabulary-of-terms.adoc) — names used throughout the project: Sign, Intra, Secret, token, Deep Sleep, OTA, SPIFFS. +2. [About the Project](02-about-the-project.adoc) — what the Sign displays and which services it talks to. +3. [Contractor's Requirements](03-contractors-requirements.adoc) — the original requirements and how the finished device matches them. +4. [General Description of the Program Run](04-program-run-overview.adoc) — one exam day, from night sleep through reservation, exam, and back to the cluster number. +5. [Program Run Step-by-Step](05-program-run-step-by-step.adoc) — boot, battery check, cluster-number mode, and where the cycle can end. +6. [How to Build the Sign Yourself](06-how-to-build-the-sign.adoc) — parts list, display pinout, build steps, and hardware alternatives. +7. [Getting Ready to Maintain and Develop the Project](07-development-environment.adoc) — Arduino IDE, libraries, credentials, and USB upload. +8. [Updating the Program Using Cloud-Pull OTA](08-cloud-pull-ota-updates.adoc) — manifest, GitHub Releases, Telegram / button / weekly triggers. +9. [Uploading the Program as Compiled Binary](09-uploading-compiled-binary.adoc) — flashing a `.bin` with `esptool` when USB Arduino upload is not an option. +10. [Firmware Rollback](10-firmware-rollback.adoc) — how a bad OTA image is detected and the previous partition is restored. +11. [Hardware Maintenance](11-hardware-maintenance.adoc) — charging the Sign and taking it off the wall. +12. [Functions Reference](12-functions-reference.adoc) — program files, `config.h`, and function descriptions. +13. [Architectural Decisions Explained](13-architectural-decisions.adoc) — why `setup()` order, `ota.h` placement, display state, and related choices exist. +14. [How to Get Exams Info from Intra](14-intra-api.adoc) — API steps, curl examples, and example server responses. +15. [Exam Simulation](15-exam-simulation.adoc) — the `EXAM_SIMULATION` macro for testing exam mode without a real exam. +16. [Create Your Own Graphics](16-create-your-own-graphics.adoc) — turning artwork into bitmaps the display can draw. +17. [How to Draw on the Display](17-how-to-draw-on-the-display.adoc) — GxEPD2 coordinates, partial windows, and text bounds. +18. [The Intricacies of Time Keeping](18-time-keeping.adoc) — UTC from Intra, time zones, and EU daylight-saving rules. +19. [Service Messages Meaning](19-service-messages.adoc) — what the display and Telegram chat show, and what to do about it. +20. [Libraries and Their Use](20-libraries.adoc) — Arduino IDE / board versions and every library the project depends on. +21. [Bugs and Suggestions How to Fix Them](21-known-bugs-and-fixes.adoc) — known display, flash, and related problems. +22. [New Bugs and Future Development Suggestions](22-reporting-and-suggestions.adoc) — where to file issues and ideas. +23. [Suggestions for Dealing with Confidential Information](23-confidential-information.adoc) — stub; not yet written (see issue #71). +24. [External Information Sources](24-external-sources.adoc) — datasheets, APIs, and other references. + +## Chapters not yet written + +These topics were listed as `//TO-DO` in the original Word document and are tracked as follow-up issues: + +- [Hardware description](https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign/issues/65) +- [Circuit diagrams and schematics](https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign/issues/66) +- [Description of the program constants](https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign/issues/67) +- [Power management](https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign/issues/68) +- [Safety considerations](https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign/issues/69) +- [The Don'ts of changing the program](https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign/issues/70) +- [Suggestions for dealing with confidential information](https://github.com/RomanAlexandroff/42-Smart-Cluster-Sign/issues/71) (chapter 23 above) diff --git a/docs/tech_documentation/book.adoc b/docs/tech_documentation/book.adoc new file mode 100644 index 0000000..22d19d9 --- /dev/null +++ b/docs/tech_documentation/book.adoc @@ -0,0 +1,65 @@ +// GitHub's web view does not process include:: directives, so this file +// looks empty-ish on GitHub. That is expected. Browse the documentation +// from README.md instead. This file exists only for the PDF build +// (`make docs`). += 42 Smart Cluster Sign +Technical Documentation +:doctype: book +:revdate: July 2026 +:toc: +:toclevels: 2 +:sectnums: +:imagesdir: media +:pdf-page-size: A4 +:title-page: +:icons: font + +This document contains detailed information about the project and was created as the go-to place to find answers to all of your HOWs and WHYs, so the software support and further project development are easier. + +include::01-vocabulary-of-terms.adoc[leveloffset=+1] + +include::02-about-the-project.adoc[leveloffset=+1] + +include::03-contractors-requirements.adoc[leveloffset=+1] + +include::04-program-run-overview.adoc[leveloffset=+1] + +include::05-program-run-step-by-step.adoc[leveloffset=+1] + +include::06-how-to-build-the-sign.adoc[leveloffset=+1] + +include::07-development-environment.adoc[leveloffset=+1] + +include::08-cloud-pull-ota-updates.adoc[leveloffset=+1] + +include::09-uploading-compiled-binary.adoc[leveloffset=+1] + +include::10-firmware-rollback.adoc[leveloffset=+1] + +include::11-hardware-maintenance.adoc[leveloffset=+1] + +include::12-functions-reference.adoc[leveloffset=+1] + +include::13-architectural-decisions.adoc[leveloffset=+1] + +include::14-intra-api.adoc[leveloffset=+1] + +include::15-exam-simulation.adoc[leveloffset=+1] + +include::16-create-your-own-graphics.adoc[leveloffset=+1] + +include::17-how-to-draw-on-the-display.adoc[leveloffset=+1] + +include::18-time-keeping.adoc[leveloffset=+1] + +include::19-service-messages.adoc[leveloffset=+1] + +include::20-libraries.adoc[leveloffset=+1] + +include::21-known-bugs-and-fixes.adoc[leveloffset=+1] + +include::22-reporting-and-suggestions.adoc[leveloffset=+1] + +include::23-confidential-information.adoc[leveloffset=+1] + +include::24-external-sources.adoc[leveloffset=+1] diff --git a/docs/tech_documentation/media/battery-empty.jpeg b/docs/tech_documentation/media/battery-empty.jpeg new file mode 100644 index 0000000..ed5fcf2 Binary files /dev/null and b/docs/tech_documentation/media/battery-empty.jpeg differ diff --git a/docs/tech_documentation/media/could-not-check-exams.png b/docs/tech_documentation/media/could-not-check-exams.png new file mode 100644 index 0000000..380fc2c Binary files /dev/null and b/docs/tech_documentation/media/could-not-check-exams.png differ diff --git a/docs/tech_documentation/media/cover-shadow.jpeg b/docs/tech_documentation/media/cover-shadow.jpeg new file mode 100644 index 0000000..ffdd89d Binary files /dev/null and b/docs/tech_documentation/media/cover-shadow.jpeg differ diff --git a/docs/tech_documentation/media/intra-token-expires.png b/docs/tech_documentation/media/intra-token-expires.png new file mode 100644 index 0000000..0199af1 Binary files /dev/null and b/docs/tech_documentation/media/intra-token-expires.png differ diff --git a/docs/tech_documentation/media/low-battery-note.jpeg b/docs/tech_documentation/media/low-battery-note.jpeg new file mode 100644 index 0000000..730c513 Binary files /dev/null and b/docs/tech_documentation/media/low-battery-note.jpeg differ diff --git a/docs/tech_documentation/media/ota-update-canceled.png b/docs/tech_documentation/media/ota-update-canceled.png new file mode 100644 index 0000000..ecd1276 Binary files /dev/null and b/docs/tech_documentation/media/ota-update-canceled.png differ diff --git a/docs/tech_documentation/media/ota-update-failed.png b/docs/tech_documentation/media/ota-update-failed.png new file mode 100644 index 0000000..b1a1512 Binary files /dev/null and b/docs/tech_documentation/media/ota-update-failed.png differ diff --git a/docs/tech_documentation/media/ota-update-successful.png b/docs/tech_documentation/media/ota-update-successful.png new file mode 100644 index 0000000..3e016b8 Binary files /dev/null and b/docs/tech_documentation/media/ota-update-successful.png differ diff --git a/docs/tech_documentation/media/ota-waiting-update-full.png b/docs/tech_documentation/media/ota-waiting-update-full.png new file mode 100644 index 0000000..2925681 Binary files /dev/null and b/docs/tech_documentation/media/ota-waiting-update-full.png differ diff --git a/docs/tech_documentation/media/ota-waiting-update.png b/docs/tech_documentation/media/ota-waiting-update.png new file mode 100644 index 0000000..0e9cddb Binary files /dev/null and b/docs/tech_documentation/media/ota-waiting-update.png differ diff --git a/docs/tech_documentation/media/telegram-bot-error.png b/docs/tech_documentation/media/telegram-bot-error.png new file mode 100644 index 0000000..0146ffa Binary files /dev/null and b/docs/tech_documentation/media/telegram-bot-error.png differ