├── .editorconfig ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.md │ └── FEATURE_REQUEST.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── bin ├── install-service.sh ├── install.sh ├── kill-matrix-server.sh ├── mkbuild.sh └── start-matrix-server.sh ├── client ├── CMakeLists.txt ├── cmake.files │ ├── FindBoost.cmake │ ├── FindSDL2.cmake │ └── FindSDL2_image.cmake ├── examples │ ├── direct-draw-on-buffer.cpp │ ├── direct-draw-on-buffer.ini │ ├── draw-mp4.cpp │ ├── draw-mp4.ini │ ├── draw-with-sdl2.cpp │ ├── draw-with-sdl2.ini │ └── modite-adventure.mp4 ├── mkbuild.sh └── src │ ├── NetworkDisplay.cpp │ ├── NetworkDisplay.h │ ├── NetworkDisplayConfig.h │ ├── SDL2Display.h │ ├── SegmentClient.cpp │ └── SegmentClient.h ├── interim-docs ├── 00 Project overview.pdf ├── 01 Frame assembly.pdf ├── 02 Raspberry Pi Setup [ Hardware ] .pdf └── 03.1 Raspberry Pi Setup [ Software from DietPi base image - long version ] .pdf ├── lib └── ini │ ├── INIReader.cpp │ ├── INIReader.h │ ├── ini.c │ └── ini.h ├── md ├── Client_setup_guide.md ├── DEVELOPING.md ├── Display_setup_guide.md ├── How_it_works.md ├── Server_setup_guide.md ├── Setup_guide.md └── img │ ├── how_it_works │ ├── client-data-flow.png │ ├── complex-example.png │ ├── server-data-flow.png │ └── simple-setup-1.png │ └── modus.logo.svg ├── sdl2-server-dummy ├── CMakeLists.txt ├── FindBoost.cmake ├── Makefile ├── main.cpp └── mkbuild.sh └── server ├── CMakeLists.txt ├── make-and-run.sh ├── pidisplay.service ├── rgb-server.ini └── src ├── MatrixSegment.cpp ├── MatrixSegment.h ├── NetworkServer.cpp ├── NetworkServer.h ├── NetworkServerConfig.h └── main.cpp /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | end_of_line = lf 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | 7 | [*.{js,vue,css}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | continuation_indent_size = 6 12 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Open Source Code Of Conduct 2 | 3 | This code of conduct outlines our expectations for participants within the Modus Create community, as well as steps to reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and expect our code of conduct to be honored. Anyone who violates this code of conduct may be banned from the community. 4 | 5 | Our open source community strives to: 6 | 7 | #### Be friendly and patient. 8 | 9 | #### Be welcoming 10 | 11 | We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. 12 | 13 | #### Be considerate 14 | 15 | Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we’re a world-wide community, so you might not be communicating in someone else’s primary language. 16 | 17 | #### Be respectful 18 | 19 | Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. 20 | Be careful in the words that you choose: we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren’t acceptable. This includes, but is not limited to: 21 | 22 | - Violent threats or language directed against another person. 23 | - Discriminatory jokes and language. 24 | - Posting sexually explicit or violent material. 25 | - Posting (or threatening to post) other people’s personally identifying information (“doxing”). 26 | - Personal insults, especially those using racist or sexist terms. 27 | - Unwelcome sexual attention. 28 | - Advocating for, or encouraging, any of the above behavior. 29 | - Repeated harassment of others. In general, if someone asks you to stop, then stop. 30 | 31 | #### When we disagree, try to understand why 32 | 33 | Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. 34 | 35 | #### Remember that we’re different 36 | 37 | The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes. 38 | 39 | This code is not exhaustive or complete. It serves to distill our common understanding of a collaborative, shared environment, and goals. We expect it to be followed in spirit as much as in the letter. 40 | 41 | ## Diversity Statement 42 | 43 | We encourage everyone to participate and are committed to building a community for all. Although we may not be able to satisfy everyone, we all agree that everyone is equal. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong. 44 | 45 | Although this list cannot be exhaustive, we explicitly honor diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected characteristics above, including participants with disabilities. 46 | 47 | ## Reporting Issues 48 | 49 | If you experience or witness unacceptable behavior—or have any other concerns—please report it by contacting us via `opensource@moduscreate.com`. All reports will be handled with discretion. In your report please include: 50 | 51 | - Your contact information. 52 | - Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link. 53 | - Any additional information that may be helpful. 54 | 55 | After filing a report, a representative will contact you personally. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. A representative will then review the incident, follow up with any additional questions, and make a decision as to how to respond. We will respect confidentiality requests for the purpose of protecting victims of abuse. 56 | 57 | Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual engages in unacceptable behavior, the representative may take any action they deem appropriate, up to and including a permanent ban from our community without warning. 58 | 59 | This Code Of Conduct follows the [template](http://todogroup.org/opencodeofconduct/) established by the [TODO Group](http://todogroup.org/). 60 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Project 2 | 3 | ### Code of Conduct 4 | 5 | Modus has adopted a [Code of Conduct](./CODE_OF_CONDUCT.md) that we expect project participants to adhere to. 6 | 7 | ### Submitting a Pull Request 8 | 9 | If you are a first time contributor, you can learn how from this _free_ series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). 10 | 11 | ### License 12 | 13 | By contributing, you agree that your contributions will belicensed under it's [license](../LICENSE). 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | --- 5 | 6 | **Environment:** 7 | 8 | **Application version** 9 | 10 | - App version or git commit number + branch 11 | - App location (local, dev, test, stage, prod) 12 | - Any paticular configuration of the app 13 | 14 | **Desktop (please complete the following information):** 15 | 16 | - OS: [e.g. iOS] 17 | - Browser [e.g. chrome, safari] 18 | - Version [e.g. 22] 19 | 20 | **Smartphone (please complete the following information):** 21 | 22 | - Device: [e.g. iPhone6] 23 | - OS: [e.g. iOS8.1] 24 | - Browser [e.g. stock browser, safari] 25 | - Version [e.g. 22] 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | 30 | **To Reproduce** 31 | Steps to reproduce the behavior: 32 | 33 | 1. Go to '...' 34 | 2. Click on '....' 35 | 3. Scroll down to '....' 36 | 4. See error 37 | 38 | **Expected behavior** 39 | A clear and concise description of what you expected to happen. 40 | 41 | **Actual behavior** 42 | A clear and concise description of what is actually happening. 43 | 44 | **Screenshots or videos** 45 | If applicable, add screenshots or videos to help explain your problem. 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | **Is your feature request related to a problem? Please describe.** 7 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 8 | 9 | **Describe the solution you'd like** 10 | A clear and concise description of what you want to happen. 11 | 12 | **Describe alternatives you've considered** 13 | A clear and concise description of any alternative solutions or features you've considered. 14 | 15 | **Additional context** 16 | Add any other context or screenshots about the feature request here. 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Change 2 | 3 | {Describe what kind of change does this PR introduce} 4 | 5 | ### Does this PR introduce a breaking change? 6 | 7 | {...} 8 | 9 | ### What needs to be documented once your changes are merged? 10 | 11 | {...} 12 | 13 | ### Additional Comments 14 | 15 | {...} 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | node_modules 3 | 4 | # .env files 5 | .env.* 6 | 7 | # Log files 8 | *.log 9 | 10 | # Filesystem descriptors 11 | .DS_Store 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw* 21 | 22 | # Use either yarn.lock or package-lock.json 23 | # Uncomment one of them to mainain a single lockfile 24 | # package-lock.json 25 | # yarn.lock 26 | 27 | ## Project-specific files 28 | cmake-build* 29 | build/ 30 | .idea/ 31 | .DS_Store 32 | .git 33 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "server/rpi-rgb-led-matrix"] 2 | path = server/rpi-rgb-led-matrix 3 | url = https://github.com/hzeller/rpi-rgb-led-matrix.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011-present, Modus Create, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Network Matrix Display 3 | 4 | Network Matrix Display is a set of libraries that allows you create a scalable network of matrix displays using Raspberry Pi Single Board Computers. It does this by using TCIP/IP as a transport method for the display data and is broken into two discrete sections; Client and Server. 5 | 6 | Please refer to our [how it works](./md/How_it_works.md) document for a detailed description of this projects functionality. 7 | 8 | This project is choc-full of dependencies and is not just for the faint of heart. It requires you to put in effort to set things up. 9 | 10 | It's worth noting that the scale of [our project](./md/example_project.md) was not the cheapest solution for the desired result. The purpose of this project was to demonstrate what can be done with a hand full of available parts and open source libraries. 11 | For the cheapest solution, we recommend display controllers like [these](https://www.aliexpress.com/item/32922416742.html). 12 | 13 | ## Related documents: 14 | - [How it works](./md/How_it_works.md) 15 | - [Client Setup Guide](./md/Client_setup_guide.md) 16 | - [Server Setup Guide](./md/Server_setup_guide.md) 17 | - [Display Setup Guide](./md/Display_setup_guide.md) 18 | 19 | ## General requirements 20 | - Good knowledge in C++ 21 | - Basic understanding of how RGB Matrices work 22 | - Parts for your project 23 | 24 | ## Client Requirements: 25 | - [LibBoost 1.7.0](https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz) 26 | - [CMake 3.14+](https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4.tar.gz) 27 | - [SDL2 (optional)](https://www.libsdl.org/download-2.0.php) 28 | 29 | ## Server Requirements 30 | - [LibBoost 1.7.0](https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz) 31 | - [CMake 3.14+](https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4.tar.gz) 32 | - [rpi-rgb-led-matrix library](https://github.com/hzeller/rpi-rgb-led-matrix) 33 | - [DietPi or similar light-weight linux distribution](https://dietpi.com/) 34 | - [Raspberry Pi 2 or greater SBC](https://www.raspberrypi.org/products/raspberry-pi-3-model-b-plus/) 35 | - [Electro Dragon RGB Panel driver board](https://www.electrodragon.com/product/rgb-matrix-panel-drive-board-raspberry-pi/) (*recommended*) 36 | - [1+ RGB Matrices](https://www.adafruit.com/product/420) 37 | 38 | ## Additional hardware 39 | - For Full Motion video, GigaBit Network switch 40 | - For low-framerate animations or periodic switches to static images, WIFI is fine 41 | - Frame for your matrix 42 | - Power supplies for your SBCs 43 | - Power supply for your RGB Matrix 44 | 45 | ## Installation 46 | Install DietPi on a Raspberry Pi 2 or greater. Make sure it has Internet connectivity. Then run this script to provision the system and install the Network Matrix Display project: 47 | 48 | curl https://raw.githubusercontent.com/ModusCreateOrg/network-rgb-matrix-display/master/bin/install.sh | bash 49 | 50 | If you want to perform these steps by hand, be sure to clone the `network-rgb-matrix-display` with the `--recursive --submodules` option. 51 | 52 | If you forget to clone it this way, you can fix up your checked out copy by issuing this command: 53 | 54 | git submodule update --init --recursive 55 | 56 | ## Client example. 57 | The following example will generate the proper network display client configuration and kick off 58 | ``` 59 | NetworkDisplayConfig displayConfig; 60 | 61 | displayConfig.frameRate = 60; // -1 to disable 62 | 63 | // This is your source display dimensions 64 | displayConfig.inputScreenWidth = 320; 65 | displayConfig.inputScreenHeight = 240; 66 | 67 | // Dimensions for your panels 68 | displayConfig.singlePanelWidth = 64; 69 | displayConfig.singlePanelHeight = 64; 70 | 71 | // How you will lay out your segments 72 | displayConfig.segmentPanelsTall = 3; 73 | displayConfig.segmentPanelsWide = 1; 74 | 75 | // How you will lay out your panels per segment 76 | displayConfig.totalPanelsWide = 5; 77 | displayConfig.totalPanelsTall = 3; 78 | 79 | displayConfig.totalSegments = 5; 80 | 81 | displayConfig.destinationPort = "9890"; 82 | 83 | // In this scheme, the last IP address octet rolls up for multiple segments. 84 | // Segment 1 is 201, segment 2 is 202, etc.. 85 | displayConfig.destinationIP = "10.0.1.20%i"; 86 | displayConfig.destinationIpStartDigit = 1; 87 | 88 | displayConfig.outputScreenWidth = displayConfig.singlePanelWidth * displayConfig.totalPanelsWide; 89 | displayConfig.outputScreenHeight = displayConfig.singlePanelHeight * displayConfig.totalPanelsTall; 90 | 91 | NetworkDisplay *networkDisplay = new NetworkDisplay(displayConfig); 92 | 93 | std::thread(interrupterThread).detach(); 94 | 95 | uint16_t color = 0; 96 | while (! interrupt_received) { 97 | // Your code could easily populate the input buffer (array of uint16_t) with the pixel data 98 | memset(networkDisplay->GetInputBuffer(), color += 1, networkDisplay->GetInputBufferSize()); 99 | 100 | // Flush the display buffer to the network 101 | networkDisplay->Update(); 102 | } 103 | ``` 104 | 105 | ## License 106 | This library is licensed under [MIT](./LICENSE). 107 | 108 | # Contributing 109 | Interested in contributing? Please see our [contribution](.github/CONTRIBUTING.md) and [code of conduct](.github/CODE_OFCONDUCT.md) guidelines. 110 | 111 | 112 | [![Modus Create](./md/img/modus.logo.svg)](https://moduscreate.com) 113 | -------------------------------------------------------------------------------- /bin/install-service.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # install-service.sh 4 | # 5 | # Install the systemd service that supports the LED matrix display 6 | 7 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 8 | set -euo pipefail 9 | IFS=$'\n\t' 10 | 11 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 12 | ${DEBUG:-false} && set -vx 13 | # Credit to https://stackoverflow.com/a/17805088 14 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 15 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 16 | 17 | # Credit to http://stackoverflow.com/a/246128/424301 18 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 19 | BASE_DIR="$DIR/.." 20 | SERVER_DIR="$BASE_DIR/server" 21 | CFG="$SERVER_DIR/pidisplay.service" 22 | 23 | cp "$CFG" /etc/systemd/system/pidisplay.service 24 | systemctl daemon-reload 25 | systemctl enable pidisplay 26 | systemctl start pidisplay 27 | -------------------------------------------------------------------------------- /bin/install.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env bash 3 | # 4 | # install.sh 5 | # 6 | # Install the Network RGB Matrix Display 7 | 8 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 9 | set -euo pipefail 10 | IFS=$'\n\t' 11 | 12 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 13 | ${DEBUG:-false} && set -vx 14 | # Credit to https://stackoverflow.com/a/17805088 15 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 16 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 17 | 18 | DIR="/root" 19 | 20 | echo "***** Install core libraries" 21 | apt-get update -y 22 | apt-get upgrade -y 23 | apt-get dist-upgrade -y 24 | apt-get install g++ vim make libsdl2-dev libsdl2-image-dev rsync git -y 25 | 26 | TMP_DIR="$(mktemp -d)" 27 | function finish { 28 | RETCODE=$? 29 | if [[ "$RETCODE" -ne 0 ]]; then 30 | echo "***** Exiting with return code $RETCODE. Temp dir $TMP_DIR contents:" 31 | ls -l "$DIR" 32 | fi 33 | rm -rf "$TMP_DIR" 34 | return "$RETCODE" 35 | } 36 | trap finish EXIT 37 | 38 | cd "$TMP_DIR" 39 | 40 | echo "***** Downloading Lib Boost 1.70.0..." 41 | BOOST_DIR="$TMP_DIR/boost_1_70_0" 42 | wget -c https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz -O - | tar -xz 43 | 44 | echo "***** Downloading CMake..." 45 | wget -c https://github.com/Kitware/CMake/releases/download/v3.15.1/cmake-3.15.1.tar.gz -O - | tar -xz 46 | 47 | echo "***** Compiling Boost..." 48 | cd "$BOOST_DIR" 49 | ./bootstrap.sh 50 | ./b2 -j4 --with-iostreams --with-thread --with-headers threading=multi install 51 | # This directory is over 700MB after compiling, remove it to save /tmp space 52 | cd "$TMP_DIR" 53 | rm -rf "$BOOST_DIR" 54 | 55 | echo "***** Compiling CMake..." 56 | cd "$TMP_DIR/cmake-3.15.1" 57 | ./configure 58 | make -j 4 install 59 | cd "$DIR" 60 | 61 | echo "**** Cloning network-rgb-matrix-display..." 62 | # RGB Matrix server stuff 63 | git clone https://github.com/ModusCreateOrg/network-rgb-matrix-display.git --recurse-submodules 64 | 65 | "$DIR/network-rgb-matrix-display/bin/mkbuild.sh" 66 | 67 | echo "**** Installing service..." 68 | 69 | "$DIR/network-rgb-matrix-display/bin/install-service.sh" 70 | 71 | echo "**** Done" 72 | -------------------------------------------------------------------------------- /bin/kill-matrix-server.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # kill-matrix-server.sh 4 | # 5 | # Kills the LED matrix server 6 | 7 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 8 | set -euo pipefail 9 | IFS=$'\n\t' 10 | 11 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 12 | ${DEBUG:-false} && set -vx 13 | # Credit to https://stackoverflow.com/a/17805088 14 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 15 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 16 | 17 | PIDS=$(pgrep -i matrix-server) 18 | for pid in $PIDS; do 19 | if [[ -n "${pid}" ]]; then 20 | kill "${pid}" 21 | fi 22 | done 23 | -------------------------------------------------------------------------------- /bin/mkbuild.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # mkbuild.sh 4 | # 5 | # Build the RGB matrix server 6 | 7 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 8 | set -euo pipefail 9 | IFS=$'\n\t' 10 | 11 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 12 | ${DEBUG:-false} && set -vx 13 | # Credit to https://stackoverflow.com/a/17805088 14 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 15 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 16 | 17 | # Credit to http://stackoverflow.com/a/246128/424301 18 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 19 | BASE_DIR="$DIR/.." 20 | BUILD_DIR="$BASE_DIR/build" 21 | SERVER_DIR="$BASE_DIR/server" 22 | 23 | rm -rf "$BUILD_DIR" 24 | mkdir -p "$BUILD_DIR" 25 | 26 | cd "$BUILD_DIR" 27 | cmake "$SERVER_DIR" 28 | make -j 4 29 | 30 | cat <. This is done for portability 52 | # reasons because not all systems place things in SDL2/ (see FreeBSD). 53 | 54 | #============================================================================= 55 | # Copyright 2003-2009 Kitware, Inc. 56 | # 57 | # Distributed under the OSI-approved BSD License (the "License"); 58 | # see accompanying file Copyright.txt for details. 59 | # 60 | # This software is distributed WITHOUT ANY WARRANTY; without even the 61 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 62 | # See the License for more information. 63 | #============================================================================= 64 | # (To distribute this file outside of CMake, substitute the full 65 | # License text for the above reference.) 66 | 67 | # message("") 68 | 69 | SET(SDL2_SEARCH_PATHS 70 | ~/Library/Frameworks 71 | /Library/Frameworks 72 | /usr/local 73 | /usr 74 | /sw # Fink 75 | /opt/local # DarwinPorts 76 | /opt/csw # Blastwave 77 | /opt 78 | ${SDL2_PATH} 79 | ) 80 | 81 | 82 | FIND_PATH(SDL2_INCLUDE_DIR SDL.h 83 | HINTS 84 | $ENV{SDL2DIR} 85 | PATH_SUFFIXES include/SDL2 include 86 | PATHS ${SDL2_SEARCH_PATHS} 87 | ) 88 | 89 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 90 | set(PATH_SUFFIXES lib64 lib/x64 lib) 91 | else() 92 | set(PATH_SUFFIXES lib/x86 lib) 93 | endif() 94 | 95 | FIND_LIBRARY(SDL2_LIBRARY_TEMP 96 | NAMES SDL2 97 | HINTS 98 | $ENV{SDL2DIR} 99 | PATH_SUFFIXES ${PATH_SUFFIXES} 100 | PATHS ${SDL2_SEARCH_PATHS} 101 | ) 102 | 103 | 104 | IF(NOT SDL2_BUILDING_LIBRARY) 105 | IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") 106 | # Non-OS lineX framework versions expect you to also dynamically link to 107 | # SDL2main. This is mainly for Windows and OS lineX. Other (Unix) platforms 108 | # seem to provide SDL2main for compatibility even though they don't 109 | # necessarily need it. 110 | FIND_LIBRARY(SDL2MAIN_LIBRARY 111 | NAMES SDL2main 112 | HINTS 113 | $ENV{SDL2DIR} 114 | PATH_SUFFIXES ${PATH_SUFFIXES} 115 | PATHS ${SDL2_SEARCH_PATHS} 116 | ) 117 | ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") 118 | ENDIF(NOT SDL2_BUILDING_LIBRARY) 119 | 120 | # SDL2 may require threads on your system. 121 | # The Apple build may not need an explicit flag because one of the 122 | # frameworks may already provide it. 123 | # But for non-OSX systems, I will use the CMake Threads package. 124 | IF(NOT APPLE) 125 | FIND_PACKAGE(Threads) 126 | ENDIF(NOT APPLE) 127 | 128 | # MinGW needs an additional link flag, -mwindows 129 | # It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -mwindows 130 | IF(MINGW) 131 | SET(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "mwindows for MinGW") 132 | ENDIF(MINGW) 133 | 134 | IF(SDL2_LIBRARY_TEMP) 135 | # For SDL2main 136 | IF(NOT SDL2_BUILDING_LIBRARY) 137 | IF(SDL2MAIN_LIBRARY) 138 | SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP}) 139 | ENDIF(SDL2MAIN_LIBRARY) 140 | ENDIF(NOT SDL2_BUILDING_LIBRARY) 141 | 142 | # For OS lineX, SDL2 uses Cocoa as a backend so it must link to Cocoa. 143 | # CMake doesn't display the -framework Cocoa string in the UI even 144 | # though it actually is there if I modify a pre-used variable. 145 | # I think it has something to do with the CACHE STRING. 146 | # So I use a temporary variable until the end so I can set the 147 | # "real" variable in one-shot. 148 | IF(APPLE) 149 | SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa") 150 | ENDIF(APPLE) 151 | 152 | # For threads, as mentioned Apple doesn't need this. 153 | # In fact, there seems to be a problem if I used the Threads package 154 | # and try using this line, so I'm just skipping it entirely for OS lineX. 155 | IF(NOT APPLE) 156 | SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) 157 | ENDIF(NOT APPLE) 158 | 159 | # For MinGW library 160 | IF(MINGW) 161 | SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) 162 | ENDIF(MINGW) 163 | 164 | 165 | # Set the final string here so the GUI reflects the final state. 166 | SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found") 167 | # Set the temp variable to INTERNAL so it is not seen in the CMake GUI 168 | SET(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "") 169 | ENDIF(SDL2_LIBRARY_TEMP) 170 | 171 | # message("") 172 | 173 | INCLUDE(FindPackageHandleStandardArgs) 174 | 175 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR) 176 | -------------------------------------------------------------------------------- /client/cmake.files/FindSDL2_image.cmake: -------------------------------------------------------------------------------- 1 | # $SDLDIR is an environment variable that would correspond to the 2 | # ./configure --prefix=$SDLDIR used in building SDL. 3 | # 4 | # Created by Eric Wing. This was influenced by the FindSDL.cmake 5 | # module, but with modifications to recognize OS X frameworks and 6 | # additional Unix paths (FreeBSD, etc). 7 | 8 | #============================================================================= 9 | # Copyright 2005-2009 Kitware, Inc. 10 | # Copyright 2012 Benjamin Eikel 11 | # 12 | # Distributed under the OSI-approved BSD License (the "License"); 13 | # see accompanying file Copyright.txt for details. 14 | # 15 | # This software is distributed WITHOUT ANY WARRANTY; without even the 16 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the License for more information. 18 | #============================================================================= 19 | # (To distribute this file outside of CMake, substitute the full 20 | # License text for the above reference.) 21 | 22 | find_path(SDL2_IMAGE_INCLUDE_DIR SDL_image.h 23 | HINTS 24 | ENV SDL2IMAGEDIR 25 | ENV SDL2DIR 26 | PATH_SUFFIXES SDL2 27 | # path suffixes to search inside ENV{SDLDIR} 28 | include/SDL2 include 29 | PATHS ${SDL2_IMAGE_PATH} 30 | ) 31 | 32 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 33 | set(VC_LIB_PATH_SUFFIX lib/x64) 34 | else() 35 | set(VC_LIB_PATH_SUFFIX lib/x86) 36 | endif() 37 | 38 | find_library(SDL2_IMAGE_LIBRARY 39 | NAMES SDL2_image 40 | HINTS 41 | ENV SDL2IMAGEDIR 42 | ENV SDL2DIR 43 | PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} 44 | PATHS ${SDL2_IMAGE_PATH} 45 | ) 46 | 47 | if(SDL2_IMAGE_INCLUDE_DIR AND EXISTS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h") 48 | file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h" SDL2_IMAGE_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_IMAGE_MAJOR_VERSION[ \t]+[0-9]+$") 49 | file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h" SDL2_IMAGE_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_IMAGE_MINOR_VERSION[ \t]+[0-9]+$") 50 | file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h" SDL2_IMAGE_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_IMAGE_PATCHLEVEL[ \t]+[0-9]+$") 51 | string(REGEX REPLACE "^#define[ \t]+SDL_IMAGE_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_MAJOR "${SDL2_IMAGE_VERSION_MAJOR_LINE}") 52 | string(REGEX REPLACE "^#define[ \t]+SDL_IMAGE_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_MINOR "${SDL2_IMAGE_VERSION_MINOR_LINE}") 53 | string(REGEX REPLACE "^#define[ \t]+SDL_IMAGE_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_PATCH "${SDL2_IMAGE_VERSION_PATCH_LINE}") 54 | set(SDL2_IMAGE_VERSION_STRING ${SDL2_IMAGE_VERSION_MAJOR}.${SDL2_IMAGE_VERSION_MINOR}.${SDL2_IMAGE_VERSION_PATCH}) 55 | unset(SDL2_IMAGE_VERSION_MAJOR_LINE) 56 | unset(SDL2_IMAGE_VERSION_MINOR_LINE) 57 | unset(SDL2_IMAGE_VERSION_PATCH_LINE) 58 | unset(SDL2_IMAGE_VERSION_MAJOR) 59 | unset(SDL2_IMAGE_VERSION_MINOR) 60 | unset(SDL2_IMAGE_VERSION_PATCH) 61 | endif() 62 | 63 | set(SDL2_IMAGE_LIBRARIES ${SDL2_IMAGE_LIBRARY}) 64 | set(SDL2_IMAGE_INCLUDE_DIRS ${SDL2_IMAGE_INCLUDE_DIR}) 65 | 66 | include(FindPackageHandleStandardArgs) 67 | 68 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_image 69 | REQUIRED_VARS SDL2_IMAGE_LIBRARIES SDL2_IMAGE_INCLUDE_DIRS 70 | VERSION_VAR SDL2_IMAGE_VERSION_STRING) 71 | 72 | # for backward compatibility 73 | set(SDLIMAGE_LIBRARY ${SDL2_IMAGE_LIBRARIES}) 74 | set(SDLIMAGE_INCLUDE_DIR ${SDL2_IMAGE_INCLUDE_DIRS}) 75 | set(SDLIMAGE_FOUND ${SDL2_IMAGE_FOUND}) 76 | 77 | mark_as_advanced(SDL2_IMAGE_LIBRARY SDL2_IMAGE_INCLUDE_DIR) -------------------------------------------------------------------------------- /client/examples/direct-draw-on-buffer.cpp: -------------------------------------------------------------------------------- 1 | #undef __USE_SDL2_VIDEO__ 2 | 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "NetworkDisplay.h" 14 | #include "NetworkDisplayConfig.h" 15 | 16 | volatile bool interrupt_received = false; 17 | static void InterruptHandler(int signo) { 18 | interrupt_received = true; 19 | } 20 | 21 | 22 | void interrupterThread() { 23 | printf("Hit CTRL + C to end!\n"); 24 | 25 | while(true) { 26 | if (interrupt_received) { 27 | exit(0); 28 | } 29 | usleep(1000); 30 | } 31 | } 32 | 33 | 34 | int main(int argc, char* argv[]) { 35 | for (int i = 0; i < argc; i++) { 36 | printf("arg %i\t%s\n", i, argv[i]); 37 | }; 38 | 39 | // Exit if we have not specified an INI file 40 | if (argc < 2) { 41 | fprintf(stderr, "Fatal Error! Please specify INI file to open.\n\n"); 42 | exit(127); 43 | } 44 | 45 | NetworkDisplayConfig displayConfig = NetworkDisplay::GenerateConfig(argv[2]); 46 | 47 | 48 | // const char *file = "direct-draw-on-buffer.ini"; // This is used for debugging within CLion 49 | // NetworkDisplayConfig displayConfig = NetworkDisplay::GenerateConfig(file); 50 | 51 | displayConfig.Describe(); 52 | 53 | signal(SIGTERM, InterruptHandler); 54 | signal(SIGINT, InterruptHandler); 55 | 56 | NetworkDisplay *networkDisplay = new NetworkDisplay(displayConfig); 57 | 58 | 59 | std::thread(interrupterThread).detach(); 60 | 61 | uint16_t nY = 0; 62 | uint16_t nX = 0; 63 | uint16_t color = 0; 64 | 65 | while (! interrupt_received) { 66 | color++; 67 | uint16_t *inputBuffer = networkDisplay->GetInputBuffer(); 68 | // Fills the screen with color. that's it. 69 | memset(inputBuffer, color, networkDisplay->GetInputBufferSize()); 70 | 71 | nX++; 72 | 73 | networkDisplay->Update(); 74 | } 75 | 76 | 77 | delete networkDisplay; 78 | 79 | 80 | return 0; 81 | } 82 | 83 | 84 | -------------------------------------------------------------------------------- /client/examples/direct-draw-on-buffer.ini: -------------------------------------------------------------------------------- 1 | ; Example INI file for a screen 4 64 2 | ; broken up into 5 strips of 4 64x64 pixel screens 3 | 4 | ; This is the size of your source screen (buffer). For now, it should be the same 5 | ; size or smaller than your output screen 6 | [input_screen] ; input screen size dimensions 7 | input_screen_width = 256 8 | input_screen_height = 64 9 | frame_rate = 60 ; -1 to ignore 10 | 11 | 12 | ; This should be the dimensions for the entire remote display. 13 | ; Example: 14 | ; if 15 | ; segment_width = 256, segment_height = 64, number_segments = 3 16 | ; then 17 | ; if (Rotate:0 or Rotate:180) 18 | ; input_width = (segment_width * number_matrices), 19 | ; input_height = (segment_height * number_segments) 20 | ; else 21 | ; input_width = (segment_width * number_segments), 22 | ; input_height = (segment_height * number_matrices) 23 | [output_screen] ; input screen size dimensions 24 | output_screen_width = 256 25 | output_screen_height = 64 26 | 27 | 28 | [matrix_dimensions] ; Single matrix dimensions (for debugging purposes) 29 | width = 64 30 | height = 64 31 | 32 | ; Dimensions for a segment of the remote display (physical grouping of RGB Matrices) 33 | ; Example: 34 | ; if (Rotate:0 or Rotate:180) 35 | ; segment_width = (num_panels * single_panel_width) 36 | ; segment_height = single_panel_height 37 | ; else if (Rotate:90 or Rotate:270) 38 | ; segment_width = (num_panels * single_panel_height) 39 | ; segment_height = single_panel_width 40 | [segment_info] 41 | number_segments = 1 ; How many segments is the screen broken into? (this is ignored) 42 | segment_width = 256 ; Width of each segment 43 | segment_height = 64 ; Height of each segment 44 | 45 | [segment_num_1] ; Need a segment section for each of the total segments! 46 | port = 9890 ; Ports can be different if you wish. 47 | ip = 10.0.1.191 48 | 49 | 50 | ;[segment_num_2] ; Need a segment section for each of the total segments! 51 | ;port = 9890 52 | ;ip = 10.1.10.202 53 | ; 54 | ;[segment_num_3] ; Need a segment section for each of the total segments! 55 | ;port = 9890 56 | ;ip = 10.1.10.203 57 | ; 58 | ;[segment_num_4] ; Need a segment section for each of the total segments! 59 | ;port = 9890 60 | ;ip = 10.1.10.204 61 | ; 62 | ;[segment_num_5] ; Need a segment section for each of the total segments! 63 | ;port = 9890 64 | ;ip = 10.1.10.205 -------------------------------------------------------------------------------- /client/examples/draw-mp4.cpp: -------------------------------------------------------------------------------- 1 | #undef __USE_SDL2_VIDEO__ 2 | // This example doesn't work just yet. 3 | //FFMPEG 4 | extern "C" { 5 | #include 6 | #include 7 | #define INPUT_BUFER_SIZE 4096 8 | } 9 | 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "NetworkDisplay.h" 18 | #include "NetworkDisplayConfig.h" 19 | 20 | volatile bool interrupt_received = false; 21 | static void InterruptHandler(int signo) { 22 | interrupt_received = true; 23 | } 24 | 25 | 26 | void interrupterThread() { 27 | printf("Hit CTRL + C to end!\n"); 28 | 29 | while(true) { 30 | if (interrupt_received) { 31 | exit(0); 32 | } 33 | usleep(1000); 34 | } 35 | } 36 | 37 | 38 | // Copied from https://ffmpeg.org/doxygen/trunk/decode_video_8c-example.html (and modified) 39 | static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt) { 40 | char buf[1024]; 41 | int result; 42 | result = avcodec_send_packet(dec_ctx, pkt); 43 | 44 | if (result < 0) { 45 | fprintf(stderr, "Error sending a packet for decoding\n"); 46 | exit(1); 47 | } 48 | printf("result = %i\n", result); 49 | 50 | while (result >= 0) { 51 | result = avcodec_receive_frame(dec_ctx, frame); 52 | if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) 53 | return; 54 | else if (result < 0) { 55 | fprintf(stderr, "Error during decoding\n"); 56 | exit(1); 57 | } 58 | printf("saving frame %3d\n", dec_ctx->frame_number); 59 | fflush(stdout); 60 | /* the picture is allocated by the decoder. no need to 61 | free it */ 62 | snprintf(buf, sizeof(buf), "%d", dec_ctx->frame_number); 63 | printf("%s\n", buf); 64 | // pgm_save(frame->data[0], frame->linesize[0], 65 | // frame->width, frame->height, buf); 66 | } 67 | } 68 | 69 | int main(int argc, char* argv[]) { 70 | 71 | // for (int i = 0; i < argc; i++) { 72 | // printf("arg %i\t%s\n", i, argv[i]); 73 | // }; 74 | // 75 | // if (argc < 2) { 76 | // fprintf(stderr, "Fatal Error! Please specify INI mp4File to open.\n\n"); 77 | // exit(127); 78 | // } 79 | 80 | const char *mp4File = "draw-mp4.ini"; 81 | NetworkDisplayConfig displayConfig = NetworkDisplay::GenerateConfig(mp4File); 82 | displayConfig.Describe(); 83 | 84 | signal(SIGTERM, InterruptHandler); 85 | signal(SIGINT, InterruptHandler); 86 | 87 | NetworkDisplay *networkDisplay = new NetworkDisplay(displayConfig); 88 | 89 | const uint16_t screenWidth = networkDisplay->GetOutputScreenWidth(), 90 | screenHeight = networkDisplay->GetOutputScreenHeight(); 91 | 92 | // Detach the interrupter thread (handles CTRL+C) 93 | std::thread(interrupterThread).detach(); 94 | 95 | const AVCodec *decoder; 96 | AVCodecParserContext *parserContext; 97 | AVCodecContext *codecContext = NULL; 98 | 99 | FILE *fileHandler; 100 | AVFrame *frame; 101 | uint8_t inputBuffer[INPUT_BUFER_SIZE + AV_INPUT_BUFFER_PADDING_SIZE]; 102 | uint8_t *data; 103 | size_t dataSize; 104 | int ret; 105 | AVPacket *packet; 106 | // if (argc <= 2) { 107 | // fprintf(stderr, "Usage: %s \n" 108 | // "And check your input mp4File is encoded by mpeg1video please.\n", argv[0]); 109 | // exit(0); 110 | // } 111 | const char* fileName = "modite-adventure.mp4"; 112 | 113 | AVFormatContext *formatContext = avformat_alloc_context(); 114 | if (! formatContext) { 115 | fprintf(stderr, "Could not allocate formatContext\n"); 116 | exit(1); 117 | } 118 | 119 | if (avformat_open_input(&formatContext, fileName, NULL, NULL) != 0) { 120 | fprintf(stderr, "Could not open %s", fileName); 121 | exit(1); 122 | } 123 | 124 | printf("format %s, duration %lld us, bit_rate %lld", formatContext->iformat->name, formatContext->duration, formatContext->bit_rate); 125 | 126 | if (avformat_find_stream_info(formatContext, NULL) < 0) { 127 | fprintf(stderr, "Could not get stream information for %s\n", fileName); 128 | exit(1); 129 | } 130 | 131 | 132 | fileHandler = fopen(fileName, "rb"); 133 | if (! fileHandler) { 134 | fprintf(stderr, "Could not open %s\n", fileName); 135 | exit(1); 136 | } 137 | 138 | /*---------------*/ 139 | 140 | packet = av_packet_alloc(); 141 | if (! packet) { 142 | fprintf(stderr, "Could not allocate packet!\n"); 143 | exit(1); 144 | } 145 | 146 | /* set end of buffer to 0 (this ensures that no over-reading happens for damaged MPEG streams) */ 147 | memset(inputBuffer + INPUT_BUFER_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE); 148 | 149 | int videoWidth = formatContext->streams[0]->codecpar->width, 150 | videoHeight = formatContext->streams[0]->codecpar->height; 151 | 152 | /* find the MPEG-4 video decoder */ 153 | decoder = avcodec_find_decoder(formatContext->streams[0]->codecpar->codec_id); 154 | if (! decoder) { 155 | fprintf(stderr, "Could not allocate decoder!\n"); 156 | exit(1); 157 | } 158 | 159 | parserContext = av_parser_init(decoder->id); 160 | if (! parserContext) { 161 | fprintf(stderr, "Could not allocate parserContext!\n"); 162 | exit(1); 163 | } 164 | 165 | codecContext = avcodec_alloc_context3(decoder); 166 | if (! codecContext) { 167 | fprintf(stderr, "Could not allocate video decoder context\n"); 168 | exit(1); 169 | } 170 | 171 | 172 | if (avcodec_open2(codecContext, decoder, NULL) < 0) { 173 | fprintf(stderr, "Could not open decoder\n"); 174 | exit(1); 175 | } 176 | 177 | frame = av_frame_alloc(); 178 | if (! frame) { 179 | fprintf(stderr, "Could not allocate video frame\n"); 180 | exit(1); 181 | } 182 | 183 | frame->nb_samples = codecContext->frame_size; 184 | frame->format = codecContext->sample_fmt; 185 | frame->channel_layout = codecContext->channel_layout; 186 | 187 | 188 | uint16_t x = 0; 189 | // Loop while we have not been interrupted. 190 | while (! interrupt_received) { 191 | x++; 192 | 193 | while (! feof(fileHandler)) { 194 | if (interrupt_received) { 195 | break; 196 | } 197 | 198 | /* read raw data from the input mp4File */ 199 | dataSize = fread(inputBuffer, 1, INPUT_BUFER_SIZE, fileHandler); 200 | 201 | if (!dataSize) 202 | break; 203 | 204 | /* use the parserContext to split the data into frames */ 205 | data = inputBuffer; 206 | int got_image = 0; 207 | while (dataSize > 0) { 208 | if (interrupt_received) { 209 | break; 210 | } 211 | 212 | ret = av_parser_parse2( 213 | parserContext, 214 | codecContext, 215 | &packet->data, 216 | &packet->size, 217 | data, 218 | dataSize, 219 | AV_NOPTS_VALUE, 220 | AV_NOPTS_VALUE, 221 | 0 222 | ); 223 | 224 | if (ret < 0) { 225 | fprintf(stderr, "Error while parsing\n"); 226 | exit(1); 227 | } 228 | data += ret; 229 | dataSize -= ret; 230 | 231 | printf("dataSize %i\n", dataSize); 232 | 233 | if (packet->size) { 234 | 235 | decode(codecContext, frame, packet); 236 | networkDisplay->Update(); 237 | } 238 | } 239 | } 240 | 241 | // interrupt_received = true; 242 | 243 | 244 | 245 | } 246 | 247 | 248 | fclose(fileHandler); 249 | av_parser_close(parserContext); 250 | avcodec_free_context(&codecContext); 251 | av_frame_free(&frame); 252 | av_packet_free(&packet); 253 | 254 | delete networkDisplay; 255 | 256 | return 0; 257 | } 258 | 259 | 260 | -------------------------------------------------------------------------------- /client/examples/draw-mp4.ini: -------------------------------------------------------------------------------- 1 | ; Example INI file for a screen comprised of pi connected to four 64x64 matrices 2 | 3 | ; This is the size of your source screen (buffer). For now, it should be the same 4 | ; size or smaller than your output screen 5 | [input_screen] ; input screen size dimensions 6 | input_screen_width = 256 7 | input_screen_height = 64 8 | frame_rate = 24 ; -1 to ignore 9 | 10 | 11 | ; This should be the dimensions for the entire remote display. 12 | ; Example: 13 | ; if 14 | ; segment_width = 256, segment_height = 64, number_segments = 3 15 | ; then 16 | ; if (Rotate:0 or Rotate:180) 17 | ; input_width = (segment_width * number_matrices), 18 | ; input_height = (segment_height * number_segments) 19 | ; else 20 | ; input_width = (segment_width * number_segments), 21 | ; input_height = (segment_height * number_matrices) 22 | [output_screen] ; input screen size dimensions 23 | output_screen_width = 256 24 | output_screen_height = 64 25 | 26 | 27 | [matrix_dimensions] ; Single matrix dimensions (for debugging purposes) 28 | width = 64 29 | height = 64 30 | 31 | ; Dimensions for a segment of the remote display (physical grouping of RGB Matrices) 32 | ; Example: 33 | ; if (Rotate:0 or Rotate:180) 34 | ; segment_width = (num_panels * single_panel_width) 35 | ; segment_height = single_panel_height 36 | ; else if (Rotate:90 or Rotate:270) 37 | ; segment_width = (num_panels * single_panel_height) 38 | ; segment_height = single_panel_width 39 | [segment_info] 40 | number_segments = 1 ; How many segments is the screen broken into? (this is ignored) 41 | segment_width = 256 ; Width of each segment 42 | segment_height = 64 ; Height of each segment 43 | 44 | [segment_num_1] ; Need a segment section for each of the total segments! 45 | port = 9890 ; Ports can be different if you wish. 46 | ip = 10.0.1.191 47 | 48 | 49 | ;[segment_num_2] ; Need a segment section for each of the total segments! 50 | ;port = 9890 51 | ;ip = 10.1.10.202 52 | ; 53 | ;[segment_num_3] ; Need a segment section for each of the total segments! 54 | ;port = 9890 55 | ;ip = 10.1.10.203 56 | ; 57 | ;[segment_num_4] ; Need a segment section for each of the total segments! 58 | ;port = 9890 59 | ;ip = 10.1.10.204 60 | ; 61 | ;[segment_num_5] ; Need a segment section for each of the total segments! 62 | ;port = 9890 63 | ;ip = 10.1.10.205 -------------------------------------------------------------------------------- /client/examples/draw-with-sdl2.cpp: -------------------------------------------------------------------------------- 1 | #undef __USE_SDL2_VIDEO__ 2 | 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include "NetworkDisplay.h" 18 | #include "NetworkDisplayConfig.h" 19 | 20 | volatile bool interrupt_received = false; 21 | static void InterruptHandler(int signo) { 22 | interrupt_received = true; 23 | } 24 | 25 | 26 | void interrupterThread() { 27 | printf("Hit CTRL + C to end!\n"); 28 | 29 | while(true) { 30 | if (interrupt_received) { 31 | exit(0); 32 | } 33 | usleep(1000); 34 | } 35 | } 36 | 37 | 38 | int main(int argc, char* argv[]) { 39 | 40 | // for (int i = 0; i < argc; i++) { 41 | // printf("arg %i\t%s\n", i, argv[i]); 42 | // }; 43 | // 44 | // if (argc < 2) { 45 | // fprintf(stderr, "Fatal Error! Please specify INI file to open.\n\n"); 46 | // exit(127); 47 | // } 48 | 49 | const char *file = "draw-with-sdl2.ini"; 50 | 51 | NetworkDisplayConfig displayConfig = NetworkDisplay::GenerateConfig(file); 52 | 53 | displayConfig.Describe(); 54 | 55 | signal(SIGTERM, InterruptHandler); 56 | signal(SIGINT, InterruptHandler); 57 | 58 | NetworkDisplay *networkDisplay = new NetworkDisplay(displayConfig); 59 | 60 | const uint16_t screenWidth = networkDisplay->GetOutputScreenWidth(), 61 | screenHeight = networkDisplay->GetOutputScreenHeight(); 62 | 63 | // Detach the interrupter thread (handles CTRL+C) 64 | std::thread(interrupterThread).detach(); 65 | 66 | // Create the SDL2 RGB Surface with 16bit color depth 67 | SDL_Surface *surface = SDL_CreateRGBSurface( 68 | 0, 69 | networkDisplay->GetOutputScreenWidth(), 70 | networkDisplay->GetInputScreenHeight(), 71 | 16, // Bit depth 72 | 0, // Auto Red Mask 73 | 0, // Auto Blue Mask 74 | 0, // Auto Green Mask 75 | 0 76 | ); 77 | 78 | 79 | // Exit the program if we can't create the SDL2 surface 80 | if(surface == NULL) { 81 | fprintf(stderr, "CreateRGBSurface failed: %s\n ", SDL_GetError()); 82 | exit(1); 83 | } 84 | 85 | // Create SDL Software Renderer 86 | SDL_Renderer *renderer = SDL_CreateSoftwareRenderer(surface); 87 | 88 | int y = 0; 89 | uint16_t color = 0; 90 | int direction = 1; 91 | 92 | // Loop while we have not been interrupted. 93 | // This will draw a line for the entire width of the screen and bounce it down and up again. 94 | while (! interrupt_received) { 95 | color++; 96 | 97 | // Clear the screen (fill the buffer with black) 98 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); 99 | SDL_RenderClear(renderer); 100 | 101 | // Draw random RGB values for 102 | SDL_SetRenderDrawColor(renderer, random() & 0xFF, random() & 0xFF, random() & 0xFF,0); 103 | 104 | SDL_RenderDrawLine(renderer, 0, y, screenWidth, y); 105 | 106 | y += direction; 107 | 108 | if (y > screenHeight) { 109 | direction = -1; 110 | y = screenHeight; 111 | } 112 | 113 | if (y < 0) { 114 | direction = 1; 115 | y = 0; 116 | } 117 | 118 | uint16_t *inputBuffer = networkDisplay->GetInputBuffer(); 119 | memcpy(inputBuffer, surface->pixels, networkDisplay->GetInputBufferSize()); 120 | 121 | 122 | networkDisplay->Update(); 123 | } 124 | 125 | delete surface; 126 | delete networkDisplay; 127 | 128 | return 0; 129 | } 130 | 131 | 132 | -------------------------------------------------------------------------------- /client/examples/draw-with-sdl2.ini: -------------------------------------------------------------------------------- 1 | ; Example INI file for a screen comprised of pi connected to four 64x64 matrices 2 | 3 | ; This is the size of your source screen (buffer). For now, it should be the same 4 | ; size or smaller than your output screen 5 | [input_screen] ; input screen size dimensions 6 | input_screen_width = 256 7 | input_screen_height = 64 8 | frame_rate = 24 ; -1 to ignore 9 | 10 | 11 | ; This should be the dimensions for the entire remote display. 12 | ; Example: 13 | ; if 14 | ; segment_width = 256, segment_height = 64, number_segments = 3 15 | ; then 16 | ; if (Rotate:0 or Rotate:180) 17 | ; input_width = (segment_width * number_matrices), 18 | ; input_height = (segment_height * number_segments) 19 | ; else 20 | ; input_width = (segment_width * number_segments), 21 | ; input_height = (segment_height * number_matrices) 22 | [output_screen] ; input screen size dimensions 23 | output_screen_width = 256 24 | output_screen_height = 64 25 | 26 | 27 | [matrix_dimensions] ; Single matrix dimensions (for debugging purposes) 28 | width = 64 29 | height = 64 30 | 31 | ; Dimensions for a segment of the remote display (physical grouping of RGB Matrices) 32 | ; Example: 33 | ; if (Rotate:0 or Rotate:180) 34 | ; segment_width = (num_panels * single_panel_width) 35 | ; segment_height = single_panel_height 36 | ; else if (Rotate:90 or Rotate:270) 37 | ; segment_width = (num_panels * single_panel_height) 38 | ; segment_height = single_panel_width 39 | [segment_info] 40 | number_segments = 1 ; How many segments is the screen broken into? (this is ignored) 41 | segment_width = 256 ; Width of each segment 42 | segment_height = 64 ; Height of each segment 43 | 44 | [segment_num_1] ; Need a segment section for each of the total segments! 45 | port = 9890 ; Ports can be different if you wish. 46 | ip = 10.0.1.191 47 | 48 | 49 | ;[segment_num_2] ; Need a segment section for each of the total segments! 50 | ;port = 9890 51 | ;ip = 10.1.10.202 52 | ; 53 | ;[segment_num_3] ; Need a segment section for each of the total segments! 54 | ;port = 9890 55 | ;ip = 10.1.10.203 56 | ; 57 | ;[segment_num_4] ; Need a segment section for each of the total segments! 58 | ;port = 9890 59 | ;ip = 10.1.10.204 60 | ; 61 | ;[segment_num_5] ; Need a segment section for each of the total segments! 62 | ;port = 9890 63 | ;ip = 10.1.10.205 -------------------------------------------------------------------------------- /client/examples/modite-adventure.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/network-rgb-matrix-display/007c79fddd2b3258f50249f8da9af2c54ebeb205/client/examples/modite-adventure.mp4 -------------------------------------------------------------------------------- /client/mkbuild.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # mkbuild.sh 4 | # 5 | # Build the RGB matrix code 6 | 7 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 8 | set -euo pipefail 9 | #IFS=$'\n\t' 10 | 11 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 12 | ${DEBUG:-false} && set -vx 13 | # Credit to https://stackoverflow.com/a/17805088 14 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 15 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 16 | 17 | # Credit to http://stackoverflow.com/a/246128/424301 18 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 19 | BASE_DIR="$DIR/.." 20 | BUILD_DIR="$BASE_DIR/build" 21 | 22 | rm -rf "$BUILD_DIR" 23 | mkdir -p "$BUILD_DIR" 24 | 25 | cd "$BUILD_DIR" 26 | cmake .. 27 | make -j 4 28 | 29 | echo "next steps: cd build/" 30 | -------------------------------------------------------------------------------- /client/src/NetworkDisplay.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "NetworkDisplay.h" 10 | 11 | NetworkDisplay::NetworkDisplay(NetworkDisplayConfig config) { 12 | mConfig = config; 13 | mTotalOutputPixels = 0; 14 | 15 | #ifdef __linux__ 16 | pthread_mutex_destroy(&mMutex); 17 | pthread_mutex_init(&mMutex, NULL); 18 | #endif 19 | 20 | InitNetworkSegments(); 21 | 22 | mFrameCount = 0; 23 | mFrameRate = config.frameRate; 24 | 25 | 26 | mInputScreenWidth = config.inputScreenWidth; 27 | mInputScreenHeight = config.inputScreenHeight; 28 | 29 | mTotalInputPixels = mInputScreenWidth * mInputScreenHeight; 30 | mInputBufferSize = mTotalInputPixels * sizeof(uint16_t); 31 | 32 | mInputBuffer1 = (uint16_t *)malloc(mInputBufferSize); 33 | mInputBuffer2 = (uint16_t *)malloc(mInputBufferSize); 34 | mCurrInBuffer = mInputBuffer1; 35 | 36 | 37 | 38 | mOutputScreenWidth = config.outputScreenWidth; 39 | mOutputScreenHeight = config.outputScreenHeight; 40 | 41 | mTotalOutputPixels = mOutputScreenWidth * mOutputScreenHeight; 42 | mOutputBufferSize = mTotalOutputPixels * sizeof(uint16_t); 43 | mOutputBuffer1 = (uint16_t *)malloc(mOutputBufferSize); 44 | mOutputBuffer2 = (uint16_t *)malloc(mOutputBufferSize); 45 | mCurrOutBuffer = mOutputBuffer1; 46 | 47 | mSinglePanelWidth = config.singlePanelWidth; 48 | mSinglePanelHeight = config.singlePanelHeight; 49 | 50 | 51 | 52 | mSNow = Milliseconds(); 53 | mSNext = mSNow + 1000 / config.frameRate; 54 | 55 | StartThread(); 56 | 57 | #ifdef __USE_SDL2_VIDEO__ 58 | // SDL2Display is buggy, so please do not depend on it just yet. 59 | mSDL2Display = new SDL2Display(mOutputScreenWidth, mOutputScreenHeight); 60 | #endif 61 | 62 | 63 | } 64 | 65 | 66 | void NetworkDisplay::InitNetworkSegments() { 67 | 68 | for (uint8_t i = 0; i < mConfig.numberSegments ; i++) { 69 | SegmentClientConfig segmentConfig; 70 | 71 | segmentConfig.segmentId = i; 72 | segmentConfig.singlePanelHeight = mConfig.singlePanelHeight; 73 | segmentConfig.singlePanelWidth = mConfig.singlePanelWidth; 74 | 75 | segmentConfig.segmentWidth = mConfig.segmentWidth; 76 | segmentConfig.segmentHeight = mConfig.segmentHeight; 77 | 78 | 79 | segmentConfig.destinationPort = strdup(mConfig.segments[i].destinationPort); 80 | 81 | segmentConfig.destinationIP = strdup(mConfig.segments[i].destinationIp); 82 | 83 | mTotalOutputPixels += segmentConfig.segmentWidth * segmentConfig.segmentWidth; 84 | 85 | auto *segment = new SegmentClient(segmentConfig); 86 | mSegments.push_back(segment); 87 | 88 | segment->StartThread(); 89 | } 90 | 91 | DescribeSegments(); 92 | } 93 | 94 | 95 | 96 | void NetworkDisplay::ThreadFunction(NetworkDisplay *remoteDisplay) { 97 | uint16_t currentFrame = 0; 98 | uint16_t smallerSceen = mInputScreenWidth < mOutputScreenWidth ? mInputScreenWidth : mOutputScreenWidth; 99 | printf("Smaller screen is %s\n", (mInputScreenWidth < mOutputScreenWidth) ? "INPUT" : "OUTPUT"); 100 | 101 | while (remoteDisplay->GetThreadRunnning()) { 102 | 103 | if (remoteDisplay->GetFrameCount() == currentFrame) { 104 | usleep(50); 105 | continue; 106 | } 107 | 108 | 109 | // Chunk up the entire screen buffer into separate display segments. 110 | for (int segmentIdx = 0; segmentIdx < remoteDisplay->mSegments.size(); segmentIdx++) { 111 | SegmentClient *segment = remoteDisplay->mSegments[segmentIdx]; 112 | 113 | segment->LockMutex(); 114 | uint16_t startX = segmentIdx * segment->mSegmentWidth; 115 | 116 | const size_t numBytes = segment->mSegmentWidth * sizeof(uint16_t); 117 | 118 | for (uint16_t y = 0; y < segment->mSegmentHeight; y++) { 119 | uint16_t *screenBuffer = &mCurrOutBuffer[(y * smallerSceen) + (startX)]; 120 | uint16_t *segmentBuffer = &segment->GetInputBuffer()[y * segment->mSegmentWidth]; 121 | 122 | memcpy(segmentBuffer, screenBuffer, numBytes); 123 | } 124 | 125 | segment->UnlockMutex(); 126 | segment->SwapBuffers(); 127 | segment->IncrementFrameCount(); 128 | } 129 | 130 | currentFrame = remoteDisplay->GetFrameCount(); 131 | 132 | } 133 | 134 | printf("NetworkDisplay::ThreadFunction ended\n"); 135 | } 136 | 137 | void NetworkDisplay::Update() { 138 | LockMutex(); 139 | 140 | bzero(mCurrOutBuffer, mOutputBufferSize); 141 | size_t smallerBuffer = (mInputBufferSize < mOutputBufferSize) ? mInputBufferSize : mOutputBufferSize; 142 | memcpy(mCurrOutBuffer, mCurrInBuffer, smallerBuffer); 143 | 144 | #ifdef __USE_SDL2_VIDEO__ 145 | mSDL2Display->Update(mCurrInBuffer, mTotalInputPixels); 146 | #endif 147 | 148 | UnlockMutex(); 149 | 150 | mFrameCount++; 151 | // SwapBuffers(); 152 | NextFrameDelay(); 153 | } 154 | 155 | 156 | 157 | 158 | void NetworkDisplay::DescribeSegments() { 159 | printf("I have %lu segments!\n", mSegments.size()); 160 | for (int i = 0; i < mSegments.size(); i++) { 161 | mSegments[i]->Describe(); 162 | } 163 | } 164 | 165 | uint16_t *NetworkDisplay::GetInputBuffer() { 166 | return mCurrInBuffer; 167 | } 168 | 169 | 170 | NetworkDisplay::~NetworkDisplay() { 171 | mThreadRunning = false; 172 | usleep(100); 173 | 174 | if (mThread.joinable()) { 175 | mThread.join(); 176 | } 177 | 178 | for (int segmentIdx = 0; segmentIdx < mSegments.size(); segmentIdx++) { 179 | mSegments[segmentIdx]->StopThread(); 180 | } 181 | 182 | delete mInputBuffer1; 183 | delete mInputBuffer2; 184 | delete mOutputBuffer1; 185 | delete mOutputBuffer2; 186 | } -------------------------------------------------------------------------------- /client/src/NetworkDisplay.h: -------------------------------------------------------------------------------- 1 | #ifndef GENUS_NETWORKED_MATRIX_DISPLAY_MATRIXDISPLAY_H 2 | #define GENUS_NETWORKED_MATRIX_DISPLAY_MATRIXDISPLAY_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "SegmentClient.h" 11 | #include "SDL2Display.h" 12 | 13 | #include "NetworkDisplayConfig.h" 14 | 15 | class NetworkDisplay { 16 | public: 17 | static NetworkDisplayConfig GenerateConfig(const char *aFile) { 18 | NetworkDisplayConfig displayConfig; 19 | 20 | int error = ini_parse(aFile, ini_file_handler, &displayConfig); 21 | if (error != 0) { 22 | fprintf(stderr, "Fatal Error: Can't parse %s. Error code %i.\n", aFile, error); 23 | fflush(stderr); 24 | exit(error); 25 | } 26 | 27 | 28 | return displayConfig; 29 | } 30 | public: 31 | 32 | explicit NetworkDisplay(NetworkDisplayConfig config); 33 | ~NetworkDisplay(); 34 | 35 | void ThreadFunction(NetworkDisplay *remoteDisplay); 36 | 37 | void DescribeSegments(); 38 | void Update(); 39 | 40 | uint16_t *GetInputBuffer(); 41 | 42 | void WritePixel(uint16_t index, uint16_t color); 43 | 44 | void SwapBuffers() { 45 | LockMutex(); 46 | mCurrInBuffer = (mCurrInBuffer == mInputBuffer1) ? mInputBuffer2 : mInputBuffer1; 47 | mCurrOutBuffer = (mCurrOutBuffer == mOutputBuffer1) ? mOutputBuffer2 : mOutputBuffer1; // Goes to matrix 48 | UnlockMutex(); 49 | } 50 | 51 | uint16_t GetFrameCount() { 52 | return mFrameCount; 53 | } 54 | 55 | void Clear(uint16_t aColor = 0) { 56 | // memset(GetInputBuffer(), GetInputBufferSize(), aColor); 57 | } 58 | 59 | public: 60 | 61 | uint16_t GetOutputScreenWidth() { 62 | return mOutputScreenWidth; 63 | } 64 | uint16_t GetOutputScreenHeight() { 65 | return mOutputScreenHeight; 66 | } 67 | uint16_t GetInputScreenWidth() { 68 | return mInputScreenWidth; 69 | } 70 | uint16_t GetInputScreenHeight() { 71 | return mInputScreenHeight; 72 | } 73 | size_t GetTotalInputPixels() { 74 | return mInputBufferSize; 75 | } 76 | 77 | size_t GetTotalOutputPixels() { 78 | return mOutputBufferSize; 79 | } 80 | 81 | bool GetThreadRunnning() { 82 | return mThreadRunning; 83 | } 84 | 85 | 86 | void StartThread() { 87 | mThreadRunning = true; 88 | mThread = std::thread(&NetworkDisplay::ThreadFunction, this, this); 89 | mThread.detach(); 90 | } 91 | 92 | void LockMutex() { 93 | pthread_mutex_lock(&mMutex); 94 | } 95 | 96 | void UnlockMutex() { 97 | pthread_mutex_unlock(&mMutex); 98 | } 99 | 100 | size_t GetInputBufferSize() { 101 | return mInputBufferSize; 102 | } 103 | 104 | public: 105 | uint32_t mSNow; 106 | uint32_t mSNext; 107 | 108 | void NextFrameDelay() { 109 | if (mFrameRate < 0) { 110 | return; 111 | } 112 | 113 | mSNext = (mSNext + 1000 / mFrameRate); 114 | 115 | 116 | if (mSNow < mSNext) { 117 | uint16_t sleepTime = (mSNext - mSNow) * 1000; 118 | usleep(sleepTime); 119 | mSNow = Milliseconds(); 120 | } 121 | 122 | } 123 | 124 | uint32_t Milliseconds() { 125 | uint32_t ms; 126 | time_t s; 127 | struct timespec spec; 128 | 129 | clock_gettime(CLOCK_REALTIME, &spec); 130 | s = spec.tv_sec; 131 | ms = lround(spec.tv_nsec / 1.0e6); 132 | if (ms > 999) { 133 | s++; 134 | ms = 0; 135 | } 136 | ms += s * 1000; 137 | return ms; 138 | } 139 | 140 | 141 | private: 142 | NetworkDisplayConfig mConfig{}; 143 | 144 | int mFrameRate; 145 | 146 | void InitNetworkSegments(); 147 | 148 | std::thread mThread; 149 | 150 | 151 | 152 | size_t mInputBufferSize; 153 | uint32_t mTotalInputPixels; 154 | uint16_t *mCurrInBuffer; 155 | uint16_t *mInputBuffer1; 156 | uint16_t *mInputBuffer2; 157 | 158 | size_t mOutputBufferSize; 159 | uint32_t mTotalOutputPixels; 160 | uint16_t *mCurrOutBuffer; 161 | uint16_t *mOutputBuffer1; 162 | uint16_t *mOutputBuffer2; 163 | 164 | uint16_t mOutputScreenWidth; 165 | uint16_t mOutputScreenHeight; 166 | uint16_t mInputScreenWidth; 167 | uint16_t mInputScreenHeight; 168 | 169 | uint16_t mSinglePanelWidth; 170 | uint16_t mSinglePanelHeight; 171 | 172 | std::vector mSegments; 173 | uint16_t mFrameCount; 174 | 175 | bool mThreadRunning{}; 176 | pthread_mutex_t mMutex{}; 177 | 178 | #ifdef __USE_SDL2_VIDEO__ 179 | SDL2Display *mSDL2Display; 180 | #endif 181 | }; 182 | 183 | 184 | #endif //GENUS_MATRIX_DISPLAY_MATRIXDISPLAY_H 185 | 186 | -------------------------------------------------------------------------------- /client/src/NetworkDisplayConfig.h: -------------------------------------------------------------------------------- 1 | #ifndef NETWORK_DISPLAY_CLIENT_NETWORKDISPLAYCONFIG_H 2 | #define NETWORK_DISPLAY_CLIENT_NETWORKDISPLAYCONFIG_H 3 | 4 | 5 | #include "ini.h" 6 | 7 | 8 | #define MATCH_GROUP(s) strcmp(aSection, s) == 0 9 | #define MATCH_CONFIG(s) strcmp(aName, s) == 0 10 | 11 | static int ini_file_handler(void* aConfig, const char* aSection, const char* aName, const char* aValue); 12 | 13 | 14 | typedef struct { 15 | char *destinationPort; 16 | char *destinationIp; 17 | } NetworkDisplaySegment; 18 | 19 | typedef struct NetworkDisplayConfig { 20 | uint16_t inputScreenWidth; 21 | uint16_t inputScreenHeight; 22 | 23 | uint16_t outputScreenWidth; 24 | uint16_t outputScreenHeight; 25 | 26 | uint16_t singlePanelWidth; 27 | uint16_t singlePanelHeight; 28 | 29 | 30 | uint16_t numberSegments; 31 | uint16_t segmentWidth; 32 | uint16_t segmentHeight; 33 | uint16_t frameRate; 34 | 35 | uint16_t destinationIpStartDigit; 36 | 37 | NetworkDisplaySegment *segments; 38 | 39 | void InitSegments(uint16_t aNumSegments) { 40 | numberSegments = aNumSegments; 41 | segments = (NetworkDisplaySegment *)malloc(numberSegments * sizeof(NetworkDisplaySegment)); 42 | } 43 | 44 | 45 | void Describe() { 46 | printf("NetworkDisplayConfig: \n"); 47 | 48 | printf("\tframeRate: %i\n", frameRate); 49 | printf("\tinputStreamWidth: %i\n", inputScreenWidth); 50 | printf("\tinputStreamHeight: %i\n", inputScreenHeight); 51 | 52 | printf("\toutputScreenWidth: %i\n", outputScreenWidth); 53 | printf("\toutputScreenHeight: %i\n", outputScreenHeight); 54 | printf("\tsinglePanelWidth: %i\n", singlePanelWidth); 55 | printf("\tsinglePanelHeight: %i\n", singlePanelHeight); 56 | printf("\tnumberSegments: %i\n", numberSegments); 57 | printf("\tsegmentWidth: %i\n", segmentWidth); 58 | printf("\tsegmentHeight: %i\n", segmentHeight); 59 | printf("\tSegments (%i):\n", numberSegments); 60 | 61 | for (int i = 0; i < numberSegments; i++) { 62 | printf("\t\tSegment[%i] IP:%s, Port: %s\n", i, segments[i].destinationIp, segments[i].destinationPort); 63 | } 64 | fflush(stdout); 65 | } 66 | } NetworkDisplayConfig; 67 | 68 | 69 | // Parses a config from the .ini file. 70 | static int ini_file_handler(void* aConfig, const char* aSection, const char* aName, const char* aValue) { 71 | 72 | auto *displayConfig = (NetworkDisplayConfig*)aConfig; 73 | printf("displayConfig %p\n", displayConfig); 74 | 75 | if (MATCH_GROUP("input_screen")) { 76 | if (MATCH_CONFIG("input_screen_width")) { 77 | displayConfig->inputScreenWidth = atoi(aValue); 78 | return 1; 79 | } 80 | if (MATCH_CONFIG("input_screen_height")) { 81 | displayConfig->inputScreenHeight = atoi(aValue); 82 | return 1; 83 | } 84 | if (MATCH_CONFIG("frame_rate")) { 85 | displayConfig->frameRate = atoi(aValue); 86 | return 1; 87 | } 88 | return 0; 89 | } 90 | 91 | // This is mostly for debugging purposes and doesn't get used in any calculations 92 | if (MATCH_GROUP("matrix_dimensions")) { 93 | if (MATCH_CONFIG("width")) { 94 | displayConfig->singlePanelWidth = atoi(aValue); 95 | return 1; 96 | } 97 | if (MATCH_CONFIG("height")) { 98 | displayConfig->singlePanelHeight = atoi(aValue); 99 | return 1; 100 | } 101 | return 0; 102 | } 103 | 104 | if (MATCH_GROUP("segment_info")) { 105 | if (MATCH_CONFIG("number_segments")) { 106 | displayConfig->InitSegments(atoi(aValue)); 107 | return 1; 108 | } 109 | if (MATCH_CONFIG("segment_width")) { 110 | displayConfig->segmentWidth = atoi(aValue); 111 | return 1; 112 | } 113 | if (MATCH_CONFIG("segment_height")) { 114 | displayConfig->segmentHeight = atoi(aValue); 115 | return 1; 116 | } 117 | 118 | return 0; 119 | } 120 | 121 | if (MATCH_GROUP("output_screen")) { 122 | if (MATCH_CONFIG("output_screen_width")) { 123 | displayConfig->outputScreenWidth = atoi(aValue); 124 | return 1; 125 | } 126 | if (MATCH_CONFIG("output_screen_height")) { 127 | displayConfig->outputScreenHeight = atoi(aValue); 128 | return 1; 129 | } 130 | 131 | return 0; 132 | } 133 | 134 | 135 | // Found a segment 136 | if (strstr(aSection, "segment_num_") != NULL) { 137 | int segmentNum = -1; 138 | 139 | char *buff = strdup(aSection); 140 | char *token; 141 | 142 | int i = 0; 143 | while ((token = strsep(&buff, "_")) != NULL) { 144 | if (i == 2) { 145 | segmentNum = atoi(token); 146 | break; 147 | } 148 | 149 | i++; 150 | } 151 | 152 | if (segmentNum != -1) { 153 | // Too many segments! 154 | if (segmentNum > displayConfig->numberSegments) { 155 | fprintf( 156 | stderr, 157 | "Fatal error! Segment %i found in ini file, but greater than total segments of %i.\n", 158 | segmentNum, 159 | displayConfig->numberSegments 160 | ); 161 | 162 | return 1; 163 | } 164 | 165 | if (MATCH_CONFIG("ip")) { 166 | displayConfig->segments[segmentNum - 1].destinationIp = strdup(aValue); 167 | return 1; 168 | } 169 | if (MATCH_CONFIG("port")) { 170 | displayConfig->segments[segmentNum - 1].destinationPort = strdup(aValue); 171 | return 1; 172 | } 173 | } 174 | else { 175 | fprintf(stderr, "Fatal Error! Could not find segment number in group [%s]\n", aSection); 176 | return 0; 177 | } 178 | 179 | } 180 | return 0; 181 | } 182 | 183 | 184 | 185 | 186 | #endif //NETWORK_DISPLAY_CLIENT_NETWORKDISPLAYCONFIG_H 187 | -------------------------------------------------------------------------------- /client/src/SDL2Display.h: -------------------------------------------------------------------------------- 1 | #ifdef __USE_SDL2_VIDEO__ 2 | 3 | #ifndef NETWORK_DISPLAY_CLIENT_SDL2DISPLAY_H 4 | #define NETWORK_DISPLAY_CLIENT_SDL2DISPLAY_H 5 | 6 | #include 7 | #include 8 | 9 | 10 | class SDL2Display { 11 | public : 12 | SDL2Display(uint16_t screenWidth, uint16_t screenHeight) { 13 | mScreenWidth = screenWidth; 14 | mScreenHeight = screenHeight; 15 | 16 | SDL_Init(SDL_INIT_VIDEO); // Initialize SDL2 17 | 18 | uint16_t flags = SDL_WINDOW_OPENGL | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_RESIZABLE| SDL_WINDOW_SHOWN; 19 | 20 | // Create an application window with the following settings: 21 | mScreen = SDL_CreateWindow( 22 | "Network Display", // window title 23 | SDL_WINDOWPOS_UNDEFINED, // initial resources position 24 | SDL_WINDOWPOS_UNDEFINED, // initial y position 25 | screenWidth * 2, // Width 26 | screenHeight * 2, // Height 27 | flags // flags - see below 28 | ); 29 | 30 | SDL_SetWindowMinimumSize(mScreen, screenWidth * 2, screenHeight * 2); 31 | 32 | // Check that the window was successfully created 33 | if (mScreen == nullptr) { 34 | // In the case that the window could not be made... 35 | printf("Could not create window: %s\n", SDL_GetError()); 36 | exit(1); 37 | } 38 | 39 | mRenderer = SDL_CreateRenderer(mScreen, -1, 0); 40 | if (! mRenderer) { 41 | printf("Cannot create mRenderer %s\n", SDL_GetError()); 42 | exit(1); 43 | } 44 | 45 | SDL_RenderSetLogicalSize(mRenderer, screenWidth, screenHeight); 46 | SDL_RenderSetIntegerScale(mRenderer, SDL_TRUE); 47 | 48 | mTexture = SDL_CreateTexture( 49 | mRenderer, /// 50 | SDL_PIXELFORMAT_ARGB8888, 51 | SDL_TEXTUREACCESS_STREAMING, 52 | screenWidth, 53 | screenHeight 54 | ); 55 | 56 | 57 | if (! mTexture) { 58 | printf("Cannot create mTexture %s\n", SDL_GetError()); 59 | } 60 | 61 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 0); 62 | SDL_SetRenderDrawColor(mRenderer, 0, 0, 0, 0); 63 | SDL_RenderClear(mRenderer); 64 | SDL_GL_SetSwapInterval(1); 65 | 66 | SDL_SetRenderDrawColor(mRenderer, 0, 0, 0, 255); 67 | SDL_RenderClear(mRenderer); 68 | SDL_RenderPresent(mRenderer); 69 | 70 | // try to move window, to fix SDL2 bug on MacOS (Mojave) 71 | int x, y; 72 | SDL_GetWindowPosition(mScreen, &x, &y); 73 | SDL_SetWindowPosition(mScreen, x+1, y+1); 74 | } 75 | ~SDL2Display() { 76 | // Close and destroy the window 77 | SDL_DestroyTexture(mTexture); 78 | SDL_DestroyRenderer(mRenderer); 79 | SDL_DestroyWindow(mScreen); 80 | 81 | // Clean up 82 | SDL_Quit(); 83 | } 84 | 85 | void Update(uint16_t *pixels, uint32_t totalPixels) { 86 | // try to move window, to fix SDL2 bug on MacOS (Mojave) 87 | if (!hackInitialized){ 88 | int x, y; 89 | SDL_GetWindowPosition(mScreen, &x, &y); 90 | SDL_SetWindowPosition(mScreen, x+1, y+1); 91 | SDL_SetWindowPosition(mScreen, x, y); 92 | hackInitialized = true; 93 | } 94 | 95 | void *screenBuff; 96 | int pitch; 97 | 98 | 99 | if (0 == SDL_LockTexture(mTexture, nullptr, &screenBuff, &pitch)) { 100 | auto *screenBits = (uint32_t *)screenBuff; 101 | 102 | for (int i = 0; i < totalPixels; i++) { 103 | uint32_t aPixel = pixels[i]; 104 | // 105 | // uint8_t red = 0, 106 | // green = 0, 107 | // blue = 0, 108 | // alpha = 0; 109 | 110 | 111 | 112 | uint8_t red = (aPixel >> 16) & 0xFF; 113 | uint8_t green = (aPixel >> 8) & 0xFF; 114 | uint8_t blue = (aPixel >> 0) & 0xFF; 115 | *screenBits++ = ((uint16_t(red & 0b11111000) << 8)) | ((uint16_t(green & 0b11111100) << 3)) | (uint16_t(blue) >> 3); 116 | // *screenBits++ = aPixel; 117 | 118 | } 119 | 120 | SDL_UnlockTexture(mTexture); 121 | } 122 | else { 123 | printf("Can't lock mTexture (%s)\n", SDL_GetError()); 124 | } 125 | 126 | SDL_SetRenderDrawColor(mRenderer, 0, 0, 0, 0); 127 | SDL_RenderClear(mRenderer); 128 | SDL_RenderCopy(mRenderer, mTexture, nullptr, nullptr); // Render mTexture to entire window 129 | SDL_RenderPresent(mRenderer); // Do update 130 | } 131 | 132 | private: 133 | SDL_Window *mScreen = nullptr; 134 | SDL_Renderer *mRenderer = nullptr; 135 | SDL_Texture *mTexture = nullptr; 136 | uint16_t mScreenWidth; 137 | uint16_t mScreenHeight; 138 | bool hackInitialized = false; 139 | }; 140 | 141 | 142 | #endif //NETWORK_DISPLAY_CLIENT_SDL2DISPLAY_H 143 | 144 | 145 | #endif -------------------------------------------------------------------------------- /client/src/SegmentClient.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "SegmentClient.h" 10 | 11 | 12 | using boost::asio::ip::tcp; 13 | 14 | 15 | SegmentClient::SegmentClient(struct SegmentClientConfig config) { 16 | #ifdef __linux__ 17 | pthread_mutex_destroy(&mMutex); 18 | pthread_mutex_init(&mMutex, NULL); 19 | #endif 20 | 21 | mFrameCount = 0; 22 | mSegmentId = config.segmentId; 23 | mSinglePanelWidth = config.singlePanelWidth; 24 | mSinglePanelHeight = config.singlePanelHeight; 25 | mPixelsPerPanel = mSinglePanelWidth * mSinglePanelWidth; 26 | 27 | mSegmentWidth = config.segmentWidth; 28 | mSegmentHeight = config.segmentHeight; 29 | 30 | mTotalPixels = mSegmentWidth * mSegmentHeight; 31 | mTotalBytes = mTotalPixels * sizeof(uint16_t); 32 | 33 | 34 | mSegmentBuffer1 = (uint16_t *)malloc(mTotalBytes); 35 | mSegmentBuffer2 = (uint16_t *)malloc(mTotalBytes); 36 | 37 | mInputBuffer = mSegmentBuffer1; 38 | 39 | mDestinationIP = (char *)malloc(sizeof(config.destinationIP)); 40 | strcpy(mDestinationIP, config.destinationIP); 41 | 42 | mDestinationPort = (char *)malloc(sizeof(config.destinationPort)); 43 | strcpy(mDestinationPort, config.destinationPort); 44 | 45 | printf("SegmentClient %s (id = %i)\n", mDestinationIP, mSegmentId); 46 | } 47 | 48 | void SegmentClient::SendDataThread(SegmentClient *mySegment) { 49 | 50 | uint16_t currentFrame = 0; 51 | 52 | auto *data = (uint16_t *)malloc(mySegment->mTotalBytes); 53 | 54 | 55 | while (mySegment->GetThreadRunning()) { 56 | 57 | if (mySegment->GetFrameCount() == currentFrame) { 58 | usleep(50); 59 | continue; 60 | } 61 | 62 | try { 63 | 64 | boost::asio::io_service io_service; 65 | 66 | tcp::socket s(io_service); 67 | tcp::resolver resolver(io_service); 68 | 69 | boost::asio::connect(s, resolver.resolve({mySegment->mDestinationIP, mySegment->mDestinationPort})); 70 | 71 | mySegment->LockMutex(); 72 | uint16_t *sBuffPtr = mySegment->GetOutputBuffer(); 73 | 74 | memcpy(data, sBuffPtr, mySegment->mTotalBytes); 75 | mySegment->UnlockMutex(); 76 | 77 | size_t numBytesWritten = boost::asio::write(s, boost::asio::buffer(data, mySegment->mTotalBytes)); 78 | 79 | char reply[10]; 80 | size_t reply_length = boost::asio::read(s,boost::asio::buffer(reply, 1)); 81 | 82 | if (reply_length != 1) { 83 | printf("* BAD *\n\n"); 84 | } 85 | 86 | currentFrame = mySegment->GetFrameCount(); 87 | 88 | } 89 | catch (std::exception& e) { 90 | std::cerr << mySegment->mDestinationIP << " " << __FUNCTION__ << " Exception: " << e.what() << "\n"; 91 | } 92 | } 93 | 94 | delete data; 95 | printf("SegmentClient::SendDataThread ended %i\n", mySegment->mSegmentId); 96 | } 97 | 98 | 99 | 100 | void SegmentClient::StartThread() { 101 | mThreadRunning = true; 102 | mThread = std::thread(&SegmentClient::SendDataThread, this, this); 103 | mThread.detach(); 104 | } 105 | 106 | 107 | void SegmentClient::StopThread() { 108 | mThreadRunning = false; 109 | if (mThread.joinable()) { 110 | mThread.join(); 111 | } 112 | usleep(100); 113 | } 114 | 115 | void SegmentClient::LockMutex() { 116 | pthread_mutex_lock(&mMutex); 117 | } 118 | 119 | void SegmentClient::UnlockMutex() { 120 | pthread_mutex_unlock(&mMutex); 121 | } 122 | 123 | void SegmentClient::Describe() { 124 | printf("SegmentClient %p\n", this); 125 | printf("\tmSegmentId = %i\n", mSegmentId); 126 | printf("\tmSinglePanelWidth = %i\n", mSinglePanelWidth); 127 | printf("\tmSinglePanelHeight = %i\n", mSinglePanelHeight); 128 | printf("\tmPixelsPerPanel = %i\n", mPixelsPerPanel); 129 | printf("\tmSegmentWidth = %i\n", mSegmentWidth); 130 | printf("\tmSegmentHeight = %i\n", mSegmentHeight); 131 | printf("\tmTotalPixels = %i\n", mTotalPixels); 132 | printf("\tmTotalBytes = %lu\n", mTotalBytes); 133 | printf("\tmDestinationIP = %s\n", mDestinationIP); 134 | printf("\tmDestinationPort = %s\n", mDestinationPort); 135 | fflush(stdout); 136 | } 137 | 138 | SegmentClient::~SegmentClient() { 139 | StopThread(); 140 | 141 | 142 | delete mSegmentBuffer1; 143 | delete mSegmentBuffer2; 144 | } 145 | 146 | -------------------------------------------------------------------------------- /client/src/SegmentClient.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef GENUS_MATRIX_DISPLAY_REMOTEMATRIXSEGMENT_H 3 | #define GENUS_MATRIX_DISPLAY_REMOTEMATRIXSEGMENT_H 4 | 5 | #include 6 | 7 | struct SegmentClientConfig { 8 | uint16_t singlePanelWidth; 9 | uint16_t singlePanelHeight; 10 | uint16_t segmentWidth; 11 | uint16_t segmentHeight; 12 | uint8_t segmentId; 13 | char *destinationIP; 14 | char *destinationPort; 15 | }; 16 | 17 | class SegmentClient { 18 | public: 19 | uint8_t mSegmentId; 20 | uint16_t mSinglePanelWidth; 21 | uint16_t mSinglePanelHeight; 22 | uint16_t mPixelsPerPanel; 23 | 24 | uint16_t mSegmentWidth; 25 | uint16_t mSegmentHeight; 26 | 27 | uint16_t mTotalPixels; 28 | 29 | size_t mTotalBytes; 30 | 31 | uint16_t *mSegmentBuffer1; 32 | uint16_t *mSegmentBuffer2; 33 | 34 | char *mDestinationIP; 35 | char *mDestinationPort; 36 | 37 | 38 | public: 39 | explicit SegmentClient(struct SegmentClientConfig config); 40 | 41 | ~SegmentClient(); 42 | 43 | void SendDataThread(SegmentClient *mySegment); 44 | 45 | void LockMutex(); 46 | 47 | void UnlockMutex(); 48 | 49 | 50 | void WritePixel(uint16_t index, uint16_t color) { 51 | LockMutex(); 52 | mInputBuffer[index] = color; 53 | UnlockMutex(); 54 | } 55 | 56 | void SwapBuffers() { 57 | LockMutex(); 58 | 59 | if (mInputBuffer == mSegmentBuffer1) { 60 | mInputBuffer = mSegmentBuffer2; 61 | mOutputBuffer = mSegmentBuffer1; 62 | } 63 | else { 64 | mInputBuffer = mSegmentBuffer1; 65 | mOutputBuffer = mSegmentBuffer2; 66 | } 67 | 68 | UnlockMutex(); 69 | } 70 | 71 | uint16_t *GetInputBuffer() { 72 | return mInputBuffer; 73 | } 74 | uint16_t *GetOutputBuffer() { 75 | return mOutputBuffer; 76 | } 77 | 78 | bool GetThreadRunning() { 79 | return mThreadRunning; 80 | } 81 | 82 | void StartThread(); 83 | void StopThread(); 84 | 85 | void Describe(); 86 | 87 | uint16_t GetFrameCount() { 88 | return mFrameCount; 89 | } 90 | 91 | void IncrementFrameCount() { 92 | mFrameCount++; 93 | // printf("mySegment.id %i, frame %i\n", mSegmentId, mFrameCount); 94 | } 95 | private: 96 | volatile uint16_t mFrameCount; 97 | volatile bool mThreadRunning; 98 | 99 | uint16_t *mInputBuffer; 100 | uint16_t *mOutputBuffer; 101 | 102 | pthread_mutex_t mMutex; 103 | std::thread mThread; 104 | 105 | }; 106 | 107 | 108 | #endif //GENUS_MATRIX_DISPLAY_REMOTEMATRIXSEGMENT_H 109 | 110 | -------------------------------------------------------------------------------- /interim-docs/00 Project overview.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/network-rgb-matrix-display/007c79fddd2b3258f50249f8da9af2c54ebeb205/interim-docs/00 Project overview.pdf -------------------------------------------------------------------------------- /interim-docs/01 Frame assembly.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/network-rgb-matrix-display/007c79fddd2b3258f50249f8da9af2c54ebeb205/interim-docs/01 Frame assembly.pdf -------------------------------------------------------------------------------- /interim-docs/02 Raspberry Pi Setup [ Hardware ] .pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/network-rgb-matrix-display/007c79fddd2b3258f50249f8da9af2c54ebeb205/interim-docs/02 Raspberry Pi Setup [ Hardware ] .pdf -------------------------------------------------------------------------------- /interim-docs/03.1 Raspberry Pi Setup [ Software from DietPi base image - long version ] .pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/network-rgb-matrix-display/007c79fddd2b3258f50249f8da9af2c54ebeb205/interim-docs/03.1 Raspberry Pi Setup [ Software from DietPi base image - long version ] .pdf -------------------------------------------------------------------------------- /lib/ini/INIReader.cpp: -------------------------------------------------------------------------------- 1 | // Read an INI file into easy-to-access name/value pairs. 2 | 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | 5 | // Copyright (C) 2009-2019, Ben Hoyt 6 | 7 | // inih and INIReader are released under the New BSD license (see LICENSE.txt). 8 | // Go to the project home page for more info: 9 | // 10 | // https://github.com/benhoyt/inih 11 | 12 | #include 13 | #include 14 | #include 15 | #include "ini.h" 16 | #include "INIReader.h" 17 | 18 | using std::string; 19 | 20 | INIReader::INIReader(const string& filename) 21 | { 22 | _error = ini_parse(filename.c_str(), ValueHandler, this); 23 | } 24 | 25 | INIReader::INIReader(const char *buffer, size_t buffer_size) 26 | { 27 | string content(buffer, buffer_size); 28 | _error = ini_parse_string(content.c_str(), ValueHandler, this); 29 | } 30 | 31 | int INIReader::ParseError() const 32 | { 33 | return _error; 34 | } 35 | 36 | string INIReader::Get(const string& section, const string& name, const string& default_value) const 37 | { 38 | string key = MakeKey(section, name); 39 | // Use _values.find() here instead of _values.at() to support pre C++11 compilers 40 | return _values.count(key) ? _values.find(key)->second : default_value; 41 | } 42 | 43 | string INIReader::GetString(const string& section, const string& name, const string& default_value) const 44 | { 45 | const string str = Get(section, name, ""); 46 | return str.empty() ? default_value : str; 47 | } 48 | 49 | long INIReader::GetInteger(const string& section, const string& name, long default_value) const 50 | { 51 | string valstr = Get(section, name, ""); 52 | const char* value = valstr.c_str(); 53 | char* end; 54 | // This parses "1234" (decimal) and also "0x4D2" (hex) 55 | long n = strtol(value, &end, 0); 56 | return end > value ? n : default_value; 57 | } 58 | 59 | double INIReader::GetReal(const string& section, const string& name, double default_value) const 60 | { 61 | string valstr = Get(section, name, ""); 62 | const char* value = valstr.c_str(); 63 | char* end; 64 | double n = strtod(value, &end); 65 | return end > value ? n : default_value; 66 | } 67 | 68 | bool INIReader::GetBoolean(const string& section, const string& name, bool default_value) const 69 | { 70 | string valstr = Get(section, name, ""); 71 | // Convert to lower case to make string comparisons case-insensitive 72 | std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower); 73 | if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") 74 | return true; 75 | else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") 76 | return false; 77 | else 78 | return default_value; 79 | } 80 | 81 | bool INIReader::HasSection(const string& section) const 82 | { 83 | const string key = MakeKey(section, ""); 84 | std::map::const_iterator pos = _values.lower_bound(key); 85 | if (pos == _values.end()) 86 | return false; 87 | // Does the key at the lower_bound pos start with "section"? 88 | return pos->first.compare(0, key.length(), key) == 0; 89 | } 90 | 91 | bool INIReader::HasValue(const string& section, const string& name) const 92 | { 93 | string key = MakeKey(section, name); 94 | return _values.count(key); 95 | } 96 | 97 | string INIReader::MakeKey(const string& section, const string& name) 98 | { 99 | string key = section + "=" + name; 100 | // Convert to lower case to make section/name lookups case-insensitive 101 | std::transform(key.begin(), key.end(), key.begin(), ::tolower); 102 | return key; 103 | } 104 | 105 | int INIReader::ValueHandler(void* user, const char* section, const char* name, 106 | const char* value) 107 | { 108 | if (!name) // Happens when INI_CALL_HANDLER_ON_NEW_SECTION enabled 109 | return 1; 110 | INIReader* reader = static_cast(user); 111 | string key = MakeKey(section, name); 112 | if (reader->_values[key].size() > 0) 113 | reader->_values[key] += "\n"; 114 | reader->_values[key] += value ? value : ""; 115 | return 1; 116 | } -------------------------------------------------------------------------------- /lib/ini/INIReader.h: -------------------------------------------------------------------------------- 1 | // Read an INI file into easy-to-access name/value pairs. 2 | 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | 5 | // Copyright (C) 2009-2019, Ben Hoyt 6 | 7 | // inih and INIReader are released under the New BSD license (see LICENSE.txt). 8 | // Go to the project home page for more info: 9 | // 10 | // https://github.com/benhoyt/inih 11 | 12 | #ifndef __INIREADER_H__ 13 | #define __INIREADER_H__ 14 | 15 | #include 16 | #include 17 | 18 | // Read an INI file into easy-to-access name/value pairs. (Note that I've gone 19 | // for simplicity here rather than speed, but it should be pretty decent.) 20 | class INIReader 21 | { 22 | public: 23 | // Construct INIReader and parse given filename. See ini.h for more info 24 | // about the parsing. 25 | explicit INIReader(const std::string& filename); 26 | 27 | // Construct INIReader and parse given buffer. See ini.h for more info 28 | // about the parsing. 29 | explicit INIReader(const char *buffer, size_t buffer_size); 30 | 31 | // Return the result of ini_parse(), i.e., 0 on success, line number of 32 | // first error on parse error, or -1 on file open error. 33 | int ParseError() const; 34 | 35 | // Get a string value from INI file, returning default_value if not found. 36 | std::string Get(const std::string& section, const std::string& name, 37 | const std::string& default_value) const; 38 | 39 | // Get a string value from INI file, returning default_value if not found, 40 | // empty, or contains only whitespace. 41 | std::string GetString(const std::string& section, const std::string& name, 42 | const std::string& default_value) const; 43 | 44 | // Get an integer (long) value from INI file, returning default_value if 45 | // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). 46 | long GetInteger(const std::string& section, const std::string& name, long default_value) const; 47 | 48 | // Get a real (floating point double) value from INI file, returning 49 | // default_value if not found or not a valid floating point value 50 | // according to strtod(). 51 | double GetReal(const std::string& section, const std::string& name, double default_value) const; 52 | 53 | // Get a boolean value from INI file, returning default_value if not found or if 54 | // not a valid true/false value. Valid true values are "true", "yes", "on", "1", 55 | // and valid false values are "false", "no", "off", "0" (not case sensitive). 56 | bool GetBoolean(const std::string& section, const std::string& name, bool default_value) const; 57 | 58 | // Return true if the given section exists (section must contain at least 59 | // one name=value pair). 60 | bool HasSection(const std::string& section) const; 61 | 62 | // Return true if a value exists with the given section and field names. 63 | bool HasValue(const std::string& section, const std::string& name) const; 64 | 65 | private: 66 | int _error; 67 | std::map _values; 68 | static std::string MakeKey(const std::string& section, const std::string& name); 69 | static int ValueHandler(void* user, const char* section, const char* name, 70 | const char* value); 71 | }; 72 | 73 | #endif // __INIREADER_H__ -------------------------------------------------------------------------------- /lib/ini/ini.c: -------------------------------------------------------------------------------- 1 | /* inih -- simple .INI file parser 2 | 3 | SPDX-License-Identifier: BSD-3-Clause 4 | 5 | Copyright (C) 2009-2019, Ben Hoyt 6 | 7 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 8 | home page for more info: 9 | 10 | https://github.com/benhoyt/inih 11 | 12 | */ 13 | 14 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 15 | #define _CRT_SECURE_NO_WARNINGS 16 | #endif 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "ini.h" 23 | 24 | #if !INI_USE_STACK 25 | #include 26 | #endif 27 | 28 | #define MAX_SECTION 50 29 | #define MAX_NAME 50 30 | 31 | /* Used by ini_parse_string() to keep track of string parsing state. */ 32 | typedef struct { 33 | const char* ptr; 34 | size_t num_left; 35 | } ini_parse_string_ctx; 36 | 37 | /* Strip whitespace chars off end of given string, in place. Return s. */ 38 | static char* rstrip(char* s) 39 | { 40 | char* p = s + strlen(s); 41 | while (p > s && isspace((unsigned char)(*--p))) 42 | *p = '\0'; 43 | return s; 44 | } 45 | 46 | /* Return pointer to first non-whitespace char in given string. */ 47 | static char* lskip(const char* s) 48 | { 49 | while (*s && isspace((unsigned char)(*s))) 50 | s++; 51 | return (char*)s; 52 | } 53 | 54 | /* Return pointer to first char (of chars) or inline comment in given string, 55 | or pointer to null at end of string if neither found. Inline comment must 56 | be prefixed by a whitespace character to register as a comment. */ 57 | static char* find_chars_or_comment(const char* s, const char* chars) 58 | { 59 | #if INI_ALLOW_INLINE_COMMENTS 60 | int was_space = 0; 61 | while (*s && (!chars || !strchr(chars, *s)) && 62 | !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { 63 | was_space = isspace((unsigned char)(*s)); 64 | s++; 65 | } 66 | #else 67 | while (*s && (!chars || !strchr(chars, *s))) { 68 | s++; 69 | } 70 | #endif 71 | return (char*)s; 72 | } 73 | 74 | /* Version of strncpy that ensures dest (size bytes) is null-terminated. */ 75 | static char* strncpy0(char* dest, const char* src, size_t size) 76 | { 77 | strncpy(dest, src, size - 1); 78 | dest[size - 1] = '\0'; 79 | return dest; 80 | } 81 | 82 | /* See documentation in header file. */ 83 | int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 84 | void* user) 85 | { 86 | /* Uses a fair bit of stack (use heap instead if you need to) */ 87 | #if INI_USE_STACK 88 | char line[INI_MAX_LINE]; 89 | int max_line = INI_MAX_LINE; 90 | #else 91 | char* line; 92 | size_t max_line = INI_INITIAL_ALLOC; 93 | #endif 94 | #if INI_ALLOW_REALLOC && !INI_USE_STACK 95 | char* new_line; 96 | size_t offset; 97 | #endif 98 | char section[MAX_SECTION] = ""; 99 | char prev_name[MAX_NAME] = ""; 100 | 101 | char* start; 102 | char* end; 103 | char* name; 104 | char* value; 105 | int lineno = 0; 106 | int error = 0; 107 | 108 | #if !INI_USE_STACK 109 | line = (char*)malloc(INI_INITIAL_ALLOC); 110 | if (!line) { 111 | return -2; 112 | } 113 | #endif 114 | 115 | #if INI_HANDLER_LINENO 116 | #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) 117 | #else 118 | #define HANDLER(u, s, n, v) handler(u, s, n, v) 119 | #endif 120 | 121 | /* Scan through stream line by line */ 122 | while (reader(line, (int)max_line, stream) != NULL) { 123 | #if INI_ALLOW_REALLOC && !INI_USE_STACK 124 | offset = strlen(line); 125 | while (offset == max_line - 1 && line[offset - 1] != '\n') { 126 | max_line *= 2; 127 | if (max_line > INI_MAX_LINE) 128 | max_line = INI_MAX_LINE; 129 | new_line = realloc(line, max_line); 130 | if (!new_line) { 131 | free(line); 132 | return -2; 133 | } 134 | line = new_line; 135 | if (reader(line + offset, (int)(max_line - offset), stream) == NULL) 136 | break; 137 | if (max_line >= INI_MAX_LINE) 138 | break; 139 | offset += strlen(line + offset); 140 | } 141 | #endif 142 | 143 | lineno++; 144 | 145 | start = line; 146 | #if INI_ALLOW_BOM 147 | if (lineno == 1 && (unsigned char)start[0] == 0xEF && 148 | (unsigned char)start[1] == 0xBB && 149 | (unsigned char)start[2] == 0xBF) { 150 | start += 3; 151 | } 152 | #endif 153 | start = lskip(rstrip(start)); 154 | 155 | if (strchr(INI_START_COMMENT_PREFIXES, *start)) { 156 | /* Start-of-line comment */ 157 | } 158 | #if INI_ALLOW_MULTILINE 159 | else if (*prev_name && *start && start > line) { 160 | /* Non-blank line with leading whitespace, treat as continuation 161 | of previous name's value (as per Python configparser). */ 162 | if (!HANDLER(user, section, prev_name, start) && !error) 163 | error = lineno; 164 | } 165 | #endif 166 | else if (*start == '[') { 167 | /* A "[section]" line */ 168 | end = find_chars_or_comment(start + 1, "]"); 169 | if (*end == ']') { 170 | *end = '\0'; 171 | strncpy0(section, start + 1, sizeof(section)); 172 | *prev_name = '\0'; 173 | #if INI_CALL_HANDLER_ON_NEW_SECTION 174 | if (!HANDLER(user, section, NULL, NULL) && !error) 175 | error = lineno; 176 | #endif 177 | } 178 | else if (!error) { 179 | /* No ']' found on section line */ 180 | error = lineno; 181 | } 182 | } 183 | else if (*start) { 184 | /* Not a comment, must be a name[=:]value pair */ 185 | end = find_chars_or_comment(start, "=:"); 186 | if (*end == '=' || *end == ':') { 187 | *end = '\0'; 188 | name = rstrip(start); 189 | value = end + 1; 190 | #if INI_ALLOW_INLINE_COMMENTS 191 | end = find_chars_or_comment(value, NULL); 192 | if (*end) 193 | *end = '\0'; 194 | #endif 195 | value = lskip(value); 196 | rstrip(value); 197 | 198 | /* Valid name[=:]value pair found, call handler */ 199 | strncpy0(prev_name, name, sizeof(prev_name)); 200 | if (!HANDLER(user, section, name, value) && !error) 201 | error = lineno; 202 | } 203 | else if (!error) { 204 | /* No '=' or ':' found on name[=:]value line */ 205 | #if INI_ALLOW_NO_VALUE 206 | *end = '\0'; 207 | name = rstrip(start); 208 | if (!HANDLER(user, section, name, NULL) && !error) 209 | error = lineno; 210 | #else 211 | error = lineno; 212 | #endif 213 | } 214 | } 215 | 216 | #if INI_STOP_ON_FIRST_ERROR 217 | if (error) 218 | break; 219 | #endif 220 | } 221 | 222 | #if !INI_USE_STACK 223 | free(line); 224 | #endif 225 | 226 | return error; 227 | } 228 | 229 | /* See documentation in header file. */ 230 | int ini_parse_file(FILE* file, ini_handler handler, void* user) 231 | { 232 | return ini_parse_stream((ini_reader)fgets, file, handler, user); 233 | } 234 | 235 | /* See documentation in header file. */ 236 | int ini_parse(const char* filename, ini_handler handler, void* user) 237 | { 238 | FILE* file; 239 | int error; 240 | 241 | file = fopen(filename, "r"); 242 | if (!file) 243 | return -1; 244 | error = ini_parse_file(file, handler, user); 245 | fclose(file); 246 | return error; 247 | } 248 | 249 | /* An ini_reader function to read the next line from a string buffer. This 250 | is the fgets() equivalent used by ini_parse_string(). */ 251 | static char* ini_reader_string(char* str, int num, void* stream) { 252 | ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; 253 | const char* ctx_ptr = ctx->ptr; 254 | size_t ctx_num_left = ctx->num_left; 255 | char* strp = str; 256 | char c; 257 | 258 | if (ctx_num_left == 0 || num < 2) 259 | return NULL; 260 | 261 | while (num > 1 && ctx_num_left != 0) { 262 | c = *ctx_ptr++; 263 | ctx_num_left--; 264 | *strp++ = c; 265 | if (c == '\n') 266 | break; 267 | num--; 268 | } 269 | 270 | *strp = '\0'; 271 | ctx->ptr = ctx_ptr; 272 | ctx->num_left = ctx_num_left; 273 | return str; 274 | } 275 | 276 | /* See documentation in header file. */ 277 | int ini_parse_string(const char* string, ini_handler handler, void* user) { 278 | ini_parse_string_ctx ctx; 279 | 280 | ctx.ptr = string; 281 | ctx.num_left = strlen(string); 282 | return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, 283 | user); 284 | } -------------------------------------------------------------------------------- /lib/ini/ini.h: -------------------------------------------------------------------------------- 1 | /* inih -- simple .INI file parser 2 | 3 | SPDX-License-Identifier: BSD-3-Clause 4 | 5 | Copyright (C) 2009-2019, Ben Hoyt 6 | 7 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 8 | home page for more info: 9 | 10 | https://github.com/benhoyt/inih 11 | 12 | */ 13 | 14 | #ifndef __INI_H__ 15 | #define __INI_H__ 16 | 17 | /* Make this header file easier to include in C++ code */ 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | 24 | /* Nonzero if ini_handler callback should accept lineno parameter. */ 25 | #ifndef INI_HANDLER_LINENO 26 | #define INI_HANDLER_LINENO 0 27 | #endif 28 | 29 | /* Typedef for prototype of handler function. */ 30 | #if INI_HANDLER_LINENO 31 | typedef int (*ini_handler)(void* user, const char* section, 32 | const char* name, const char* value, 33 | int lineno); 34 | #else 35 | typedef int (*ini_handler)(void* user, const char* section, 36 | const char* name, const char* value); 37 | #endif 38 | 39 | /* Typedef for prototype of fgets-style reader function. */ 40 | typedef char* (*ini_reader)(char* str, int num, void* stream); 41 | 42 | /* Parse given INI-style file. May have [section]s, name=value pairs 43 | (whitespace stripped), and comments starting with ';' (semicolon). Section 44 | is "" if name=value pair parsed before any section heading. name:value 45 | pairs are also supported as a concession to Python's configparser. 46 | 47 | For each name=value pair parsed, call handler function with given user 48 | pointer as well as section, name, and value (data only valid for duration 49 | of handler call). Handler should return nonzero on success, zero on error. 50 | 51 | Returns 0 on success, line number of first error on parse error (doesn't 52 | stop on first error), -1 on file open error, or -2 on memory allocation 53 | error (only when INI_USE_STACK is zero). 54 | */ 55 | int ini_parse(const char* filename, ini_handler handler, void* user); 56 | 57 | /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't 58 | close the file when it's finished -- the caller must do that. */ 59 | int ini_parse_file(FILE* file, ini_handler handler, void* user); 60 | 61 | /* Same as ini_parse(), but takes an ini_reader function pointer instead of 62 | filename. Used for implementing custom or string-based I/O (see also 63 | ini_parse_string). */ 64 | int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 65 | void* user); 66 | 67 | /* Same as ini_parse(), but takes a zero-terminated string with the INI data 68 | instead of a file. Useful for parsing INI data from a network socket or 69 | already in memory. */ 70 | int ini_parse_string(const char* string, ini_handler handler, void* user); 71 | 72 | /* Nonzero to allow multi-line value parsing, in the style of Python's 73 | configparser. If allowed, ini_parse() will call the handler with the same 74 | name for each subsequent line parsed. */ 75 | #ifndef INI_ALLOW_MULTILINE 76 | #define INI_ALLOW_MULTILINE 1 77 | #endif 78 | 79 | /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of 80 | the file. See https://github.com/benhoyt/inih/issues/21 */ 81 | #ifndef INI_ALLOW_BOM 82 | #define INI_ALLOW_BOM 1 83 | #endif 84 | 85 | /* Chars that begin a start-of-line comment. Per Python configparser, allow 86 | both ; and # comments at the start of a line by default. */ 87 | #ifndef INI_START_COMMENT_PREFIXES 88 | #define INI_START_COMMENT_PREFIXES ";#" 89 | #endif 90 | 91 | /* Nonzero to allow inline comments (with valid inline comment characters 92 | specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match 93 | Python 3.2+ configparser behaviour. */ 94 | #ifndef INI_ALLOW_INLINE_COMMENTS 95 | #define INI_ALLOW_INLINE_COMMENTS 1 96 | #endif 97 | #ifndef INI_INLINE_COMMENT_PREFIXES 98 | #define INI_INLINE_COMMENT_PREFIXES ";" 99 | #endif 100 | 101 | /* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ 102 | #ifndef INI_USE_STACK 103 | #define INI_USE_STACK 1 104 | #endif 105 | 106 | /* Maximum line length for any line in INI file (stack or heap). Note that 107 | this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ 108 | #ifndef INI_MAX_LINE 109 | #define INI_MAX_LINE 200 110 | #endif 111 | 112 | /* Nonzero to allow heap line buffer to grow via realloc(), zero for a 113 | fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is 114 | zero. */ 115 | #ifndef INI_ALLOW_REALLOC 116 | #define INI_ALLOW_REALLOC 0 117 | #endif 118 | 119 | /* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK 120 | is zero. */ 121 | #ifndef INI_INITIAL_ALLOC 122 | #define INI_INITIAL_ALLOC 200 123 | #endif 124 | 125 | /* Stop parsing on first error (default is to keep parsing). */ 126 | #ifndef INI_STOP_ON_FIRST_ERROR 127 | #define INI_STOP_ON_FIRST_ERROR 0 128 | #endif 129 | 130 | /* Nonzero to call the handler at the start of each new section (with 131 | name and value NULL). Default is to only call the handler on 132 | each name=value pair. */ 133 | #ifndef INI_CALL_HANDLER_ON_NEW_SECTION 134 | #define INI_CALL_HANDLER_ON_NEW_SECTION 0 135 | #endif 136 | 137 | /* Nonzero to allow a name without a value (no '=' or ':' on the line) and 138 | call the handler with value NULL in this case. Default is to treat 139 | no-value lines as an error. */ 140 | #ifndef INI_ALLOW_NO_VALUE 141 | #define INI_ALLOW_NO_VALUE 0 142 | #endif 143 | 144 | #ifdef __cplusplus 145 | } 146 | #endif 147 | 148 | #endif /* __INI_H__ */ -------------------------------------------------------------------------------- /md/Client_setup_guide.md: -------------------------------------------------------------------------------- 1 | # Network Matrix Display Client Setup Guide 2 | 3 | 4 | ## Client Requirements: 5 | - [LibBoost 1.7.0](https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz) 6 | - [CMake 3.14+](https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4.tar.gz) 7 | - [SDL2 (optional)](https://www.libsdl.org/download-2.0.php) 8 | -------------------------------------------------------------------------------- /md/DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | This is in progress and not ready for public consumption 4 | 5 | 6 | 7 | # Developing 8 | This document's purpose is to provide a high-level understanding of how Genus work. Programmers wishing to get involved should review our [contributions](../CONTRIBUTING.md) guidelines as well as have a decent understanding of C++ and build tools. Having some knowledge of SOCs, such as the [ESP32-WROVER](https://docs.espressif.com/projects/esp-idf/en/3.2/get-started/get-started-wrover-kit.html) would also be very helpful. 9 | 10 | *Note* We've standardized on ESP-IDF version 3.2: 11 | https://github.com/espressif/esp-idf/releases/tag/v3.2 12 | https://docs.espressif.com/projects/esp-idf/en/v3.2/ 13 | 14 | 15 | ## How Genus works 16 | Genus is a cross-platform a puzzle game created by [Modus Create](https://moduscreate.com) for the 2018 holiday sesaon. Genus runs on the [ODROID GO](https://www.hardkernel.com/shop/odroid-go/), macOS and Linux. 17 | 18 | Building Genus has been tested on the `x86_64` architecture with macOS X High Sierra and Mojave, Ubuntu 16.04 and 18.04, and on the `armhf` architecture with Raspbian Stretch (9.6). 19 | 20 | The following visualization depicts the layers of the various libraries at play. 21 | ![genus-block-diagram](./img/genus-block-diagram.jpg) 22 | 23 | #### All platforms 24 | [Genus](https://github.com/moduscreateorg/genus) this game.\ 25 | [Creative Engine](https://github.com/ModusCreateOrg/creative-engine) is the game engine developed by Modus Create. It implements LibXMP, SDL2, ESP-IDF (Audio, Video and Input drivers).\ 26 | [LibXMP](http://xmp.sourceforge.net/) is a fantastic cross-platform library for playing music using the [Xtended Module (XM)](https://en.wikipedia.org/wiki/XM_(file_format)) format and also has additional functionality to play sound effects.\ 27 | 28 | [Rcomp](https://github.com/ModusCreateOrg/creative-engine/blob/master/tools/rcomp.cpp) is a CLI tool that takes any binary resources and packages (graphic, audio, etc.) them into a binary blob to be included in the game executable and is part of [Creative Engine](https://github.com/ModusCreateOrg/creative-engine). 29 | 30 | #### macOS, Linux 31 | [SDL2](https://www.libsdl.org/download-2) is a cross-platform low-level media layer framework. Creative Engine generates audio data with LibXMP and feeds it into the SDL2 audio runloo and does similar to present visuals in the application window as well as poll for keyboard input to allow for gameplay. 32 | 33 | #### ODROID GO 34 | [Espressif IoT development Framework (ESP IDF)](https://github.com/espressif/esp-idf) is a low level framework for accessing capabilities of the ESP32-WOVER SOC. 35 | 36 | 37 | ## Getting started 38 | Let's get setup for desktop and device development. To do so, we'll need to ensure that we have the right libraries and tools installed. 39 | 40 | We're going to get setup in three phases: 41 | 1. Clone Genus and Creative Engine 42 | 2. Install supporting desktop libraries and tools 43 | 3. Install ESP IDF toolchain (this is only needed if you want to program an ODROID GO) 44 | 45 | ## Clone Genus and Creative Engine 46 | The first thing we need to is create a folder that will contain Genus and Creative engine. When we're done, the folder struction will look similar to the following. 47 | 48 | projects/ 49 | |-creative-engine/ # Source Creative Engine 50 | |-genus/ # Source for Genus 51 | |-creative-engine/ # Symbolic Link to the above directory 52 | 53 | Let's clone the Genus and Creative Engine repos: 54 | 55 | mkdir genus-game/ # Whould be within ~/projects or similar 56 | cd genus-game/ 57 | git clone git@github.com:ModusCreateOrg/genus.git 58 | git clone git@github.com:ModusCreateOrg/creative-engine.git 59 | ln -s creative-engine/ genus/creative-engine # Create Symbolic Link 60 | 61 | ## Install dependencies 62 | 63 | ### macOS 64 | The macOS build requires XCode and uses [Homebrew](https://brew.sh), which it will install for you if it is not already installed. 65 | 66 | - [ ] Install [XCode](https://developer.apple.com/xcode/) 67 | - [ ] Build and run Genus 68 | ``` 69 | # Run this command from genus/ 70 | scripts/build.sh # Build Genus 71 | open build/genus.app # Run Genus 72 | ``` 73 | 74 | ### Linux (Debian based) 75 | The Debian based build, usable on Debian Stretch and Ubuntu 16.04+, will install development tool dependencies and complete the build in one step. 76 | 77 | Running the `build.sh` script will download all development dependencies, including `libsdl2-dev`, `libsdl2-image-dev`, development tools including `g++`, and will install `cmake` from either a precompiled binary or from source if a binary is not available for your architecture. 78 | 79 | - [ ] Build and run Genus 80 | ``` 81 | # Run this command from genus/ 82 | scripts/build.sh # Build Genus 83 | build/genus # Run Genus 84 | ``` 85 | 86 | ### Raspberry Pi 87 | 88 | Genus is tested on Raspbian Stretch (9.6) on the Raspberry Pi 2B+ and 3B+. You will need about 2GB of space in /tmp and about 2GB of space in /home to build this. 89 | 90 | The game will run very slowly without enabling the OpenGL desktop driver. You can enable it by running `sudo raspbi-config` and choosing _Advanced Options / GL Driver / GL (Full KMS)_. See this site for complete instructions: https://eltechs.com/how-to-enable-opengl-on-raspberry-pi/ 91 | 92 | The `build.sh` script will download all development dependencies, including `libsdl2-dev`, `libsdl2-image-dev`, development tools including `g++`, and will install `cmake` from source. 93 | 94 | - [ ] Install dependencies, build and run Genus 95 | 96 | ``` 97 | # Run this command from genus/ 98 | scripts/build.sh # Build Genus 99 | build/genus # Run Genus 100 | ``` 101 | 102 | The first time the build runs it will have to build `cmake` from source which takes a long time. It will install `cmake` in `/usr/local/bin/cmake`. 103 | 104 | ## ODROID GO/ESP32 105 | - [ ] Follow the [setup-toolchain](https://docs.espressif.com/projects/esp-idf/en/stable/get-started/#setup-toolchain) instructions for the ESP IDF. Be sure to follow them thorougly! 106 | - [ ] Stage the appropriate SDK config file 107 | ``` 108 | # Linux ONLY 109 | cp sdkconfig.linux sdkconfig 110 | # macOS ONLY 111 | cp sdkconfig.mac sdkconfig 112 | ``` 113 | - [ ] Build and run genus 114 | ``` 115 | # From within genus/ 116 | make -j 4 flash #Assuming you have four CPU cores to compile 117 | ``` 118 | 119 | ## Additional information 120 | We highly recommend using the [CLion IDE](https://www.jetbrains.com/clion/) to develop the project and run the builds. 121 | 122 | 123 | ## Git Workflow 124 | Read about [GitHub workflow](https://github.com/ModusCreateOrg/creative-engine) at the creative-engine repo. 125 | 126 | The gist is that we fork the main repos and work in our private forks. We push to our forks. We create Pull Requests against the main repos. 127 | 128 | The only way code makes it into master in the main repo is if we merge a PR. 129 | -------------------------------------------------------------------------------- /md/Display_setup_guide.md: -------------------------------------------------------------------------------- 1 | # Network Matrix Display Setup Guide -------------------------------------------------------------------------------- /md/How_it_works.md: -------------------------------------------------------------------------------- 1 | # How the Network Matrix Display works 2 | 3 | Network Matrix Display is a set of libraries that allows you create a scalable network of matrix displays using Raspberry Pi Single Board Computers. It does this by using TCIP/IP as a transport method for the display data and is broken into two discrete sections; Client and Server. 4 | 5 | ## An important word on security 6 | This library is designed for speed and thus does not use encryption by client or server libraries. The Server will run and listen on port 9890 and once it receives the right amount of information, it will happily display it. It is for these reasons that we **strongly advise** keeping these devices off of your network. It is only advised that you **temporarily** connect them to an network with internet access if for updates only. 7 | 8 | ## Client & Server definition 9 | A `Client` is responsible for generating the display data that will be sent to one or more `Servers` to display via their connected RGB Matrix panels. Utilizing this code base, there is only a single client and there can be on or more servers. 10 | 11 | For networking, this project makes heavy use of [Boost ASIO library](https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio.html) and [Henner Zeller's](https://github.com/hzeller) [rpi-rgb-led-matrix library](https://github.com/hzeller/rpi-rgb-led-matrix) (server only). 12 | 13 | *Note*: For speed and simplicity, the client and server code do not implement any sort of security (encryption, authentication, tokens, etc..) what-so-ever! It is advised that you execute this in environments with physical network separation from the internet! 14 | 15 | Let's look at some use cases. 16 | 17 | ## Simple : one client, one server (single strip of panels) 18 | This is the easiest of examples to imagine. You need to have one or more RGB Matrices fed via a remote source. Here, you would need a computer to act as a client, a Raspberry Pi to act as a server and one or more RGB Matrices. 19 | 20 | ![simple-setup-1.png](./img/how_it_works/simple-setup-1.png) 21 | 22 | The above image depicts a Pi acting in the role of a Client, but the truth is, any Linux or MacOS computer should work well. 23 | This [YouTube video](https://www.youtube.com/watch?v=gTtBLEOPpsM) is a great demonstration of the complex version of this project running on a linux PC with three Pis acting as `Servers` (pardon the wiring mess 😉). 24 | 25 | 26 | ### Client to server operation (Simple example) 27 | In the above example, we have two RGB panels connected. Each panel is 64 pixels tall and 64 pixels wide (4,096 addressable pixels). Given that we have two panels side-by-side, our entire display area is 128 pixels wide by 64 pixels tall, a total of addressable 8,192 pixels. 28 | 29 | __Client:__ The client is configured with the IP Addresses and Port of the Server. We also configure the client with the relevant display information; number of panels, dimensions of panels and how many "segments". Our current example only has a single segment, so we'll dive into that term a little later on. For every execution of the run loop, we'll generate the necessary buffer data for the Server to display and shuttle it over to the sever via TCIP/IP. Here's an image that demonstrates how the client data flow operates. 30 | 31 | ![Client Data Flow image](./img/how_it_works/client-data-flow.png) 32 | 33 | The `Run Loop` is something that executes at a fixed interval to provide the necessary display data for the remote matrices (`Server`). 34 | 35 | In this example, our `Client` code would only spawn a single `Copier Thread` as there is only a single LED matrix segment to deal with. 36 | 37 | It's worth noting that the Sender thread that executes on the segment client is non-blocking and failures to connect are graceful, thus non-impacting to your code. 38 | 39 | __Server:__ The server is configured with the same information as the server, including the receiving port number. The server looks similar to the Client with a few exceptions. 40 | 41 | ![Server Data Flow image](./img/how_it_works/server-data-flow.png) 42 | 43 | The `NetworkServer` class spawns a thread (`ReceiveDataThread`) and implements `Boost ASIO` and patiently waits (blocking) for TCP/IP connections from the client. After data is received from the client, the data is copied to an instance of `MatrixStrip`, which extends the `rpi-rgb-led-matrix` library's `ThreadedCanvasManipulator` class, and implements double buffering to ensure we don't block the render thread (`ThreadedCanvasManipulator::Run()`) as blocking this function will cause the LED panels to flicker. 44 | 45 | 46 | ## Complex : one client, many servers, many panels 47 | This project's architecture can scale much greater than a few panels on a few displays. The following image depicts an example project where we need to provide enough pixel surface area for a 320 x 240 display. 48 | 49 | ![Complex Example Image](./img/how_it_works/complex-example.png) 50 | 51 | This example works similarly to the the Client to Server communication explanation above, except for each Strip, an instance of the `SegmentClient` is instantiated, each with a specific target `Server` in mind. 52 | 53 | We use these many RaspberryPis because driving RGB LED Matrices is a CPU intensive process and the RasbperryPi 3b+ has limitations with RAM (~900Mhz) and I2S Bus Speeds and with our experimentation, found that 16,384 LEDs is as many as the device can drive without beginning to impact usability with significant flickering. 54 | 55 | The Server Raspberry Pis connect to the Matrices via a daughter board that neatly exposes the GPIO pins in HUB75 arrangement and also includes much needed 3.3V to 5V level shifters. 56 | 57 | ### Flipping the image 58 | If you peek at the wiring guide below, you'll see that the `rpi-rgb-led-matrix` wiring diagram has the Raspberry Pi to the Right of the Matrices, making the origin of the screens the top left corner of the collection of Matrices. To make our project work as we wish, we orient the buffer data flipping the image data 90 degrees clockwise. 59 | 60 | # Examples in action 61 | At the time of this writing, there is one [simple client example](../client/examples/simple.cpp) simply fills the displays with slid transitioning colors as a demonstration of execution. 62 | 63 | Other examples include our [LaiNES emulator](https://github.com/jaygarcia/LaiNES-with-network-display) offline work. To get this to run, you'll need to replicate the [complex hardware setup](https://github.com/jaygarcia/LaiNES-with-network-display/md/Hardware.md). Be aware that LaiNES is a limited emulator and has some compatibility issues with some ROMS. 64 | 65 | 66 | ## Panel data wiring guide 67 | 68 | Henner has a great [wiring guide](https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/wiring.md) in his repository and it's worth reading. It's very detailed, but it helps you wrap your mind around how the Pi is able to display data on the matrices. 69 | ![Henner's wiring guide image](../server/rpi-rgb-led-matrix/img/coordinates.png) 70 | **(Henner's wiring guide image)* 71 | 72 | If you're going run parallel chains of matrices, they must be a multiple of that parallel chain. In other words, if you're doing three parallel chains of matrices, you cannot have five total RGB Matrices. 73 | 74 | 75 | ## General requirements 76 | - Good knowledge in C++ 77 | - Basic understanding of how RGB Matrices work 78 | - Parts for your project 79 | 80 | ## Client Requirements: 81 | - [LibBoost 1.7.0](https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz) 82 | - [CMake 3.14+](https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4.tar.gz) 83 | - [SDL2 (optional)](https://www.libsdl.org/download-2.0.php) 84 | 85 | ## Server Requirements 86 | - [LibBoost 1.7.0](https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz) 87 | - [CMake 3.14+](https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4.tar.gz) 88 | - [rpi-rgb-led-matrix library](https://github.com/hzeller/rpi-rgb-led-matrix) 89 | - [DietPi or similar light-weight linux distribution](https://dietpi.com/) 90 | - [Raspberry Pi 2 or greater SBC](https://www.raspberrypi.org/products/raspberry-pi-3-model-b-plus/) 91 | - [Electro Dragon RGB Panel driver board](https://www.electrodragon.com/product/rgb-matrix-panel-drive-board-raspberry-pi/) (*recommended*) 92 | - [1+ RGB Matrices](https://www.adafruit.com/product/420) 93 | 94 | ## Additional hardware 95 | - Gigabit Network switch (*wifi is too slow*) 96 | - Frame for your matrix 97 | - Power supplies for your SBCs 98 | - Power supply for your RGB Matrix 99 | 100 | 101 | -------------------------------------------------------------------------------- /md/Server_setup_guide.md: -------------------------------------------------------------------------------- 1 | # Network Matrix Display Server Setup Guide 2 | 3 | This project is choc-full of dependencies and is not just for the faint of heart. It requires you to put in effort to set things up. 4 | 5 | ## Server Requirements 6 | - [LibBoost 1.7.0](https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz) 7 | - [CMake 3.14+](https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4.tar.gz) 8 | - [rpi-rgb-led-matrix library](https://github.com/hzeller/rpi-rgb-led-matrix) 9 | - [DietPi or similar light-weight linux distribution](https://dietpi.com/) 10 | - [Raspberry Pi 2 or greater SBC](https://www.raspberrypi.org/products/raspberry-pi-3-model-b-plus/) 11 | - [Electro Dragon RGB Panel driver board](https://www.electrodragon.com/product/rgb-matrix-panel-drive-board-raspberry-pi/) (*recommended*) 12 | - [1+ RGB Matrices](https://www.adafruit.com/product/420) -------------------------------------------------------------------------------- /md/Setup_guide.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/network-rgb-matrix-display/007c79fddd2b3258f50249f8da9af2c54ebeb205/md/Setup_guide.md -------------------------------------------------------------------------------- /md/img/how_it_works/client-data-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/network-rgb-matrix-display/007c79fddd2b3258f50249f8da9af2c54ebeb205/md/img/how_it_works/client-data-flow.png -------------------------------------------------------------------------------- /md/img/how_it_works/complex-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/network-rgb-matrix-display/007c79fddd2b3258f50249f8da9af2c54ebeb205/md/img/how_it_works/complex-example.png -------------------------------------------------------------------------------- /md/img/how_it_works/server-data-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/network-rgb-matrix-display/007c79fddd2b3258f50249f8da9af2c54ebeb205/md/img/how_it_works/server-data-flow.png -------------------------------------------------------------------------------- /md/img/how_it_works/simple-setup-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/network-rgb-matrix-display/007c79fddd2b3258f50249f8da9af2c54ebeb205/md/img/how_it_works/simple-setup-1.png -------------------------------------------------------------------------------- /md/img/modus.logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modus-Logo-Long-Black 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /sdl2-server-dummy/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(svr-dummy) 3 | 4 | set(CMAKE_CXX_STANDARD 14) 5 | 6 | # external libraries 7 | INCLUDE(FindPkgConfig) 8 | FIND_PACKAGE(Boost REQUIRED) 9 | FIND_PACKAGE(Threads REQUIRED) 10 | 11 | PKG_SEARCH_MODULE(SDL2 REQUIRED sdl2) 12 | PKG_SEARCH_MODULE(SDL2IMAGE REQUIRED SDL2_image>=2.0.0) 13 | 14 | INCLUDE_DIRECTORIES( 15 | ${Boost_INCLUDE_DIR} 16 | ${SDL2_INCLUDE_DIRS} 17 | ${SDL2IMAGE_INCLUDE_DIRS} 18 | ) 19 | #PKG_SEARCH_MODULE(BOOST REQUIRED libboost) 20 | 21 | 22 | 23 | add_executable(${PROJECT_NAME} main.cpp) 24 | TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT} -L/usr/local/lib ${SDL2_LIBRARIES} ${SDL2IMAGE_LIBRARIES} ${Boost_LIBRARIES}) 25 | 26 | 27 | -------------------------------------------------------------------------------- /sdl2-server-dummy/Makefile: -------------------------------------------------------------------------------- 1 | Makefile -------------------------------------------------------------------------------- /sdl2-server-dummy/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // blocking_tcp_echo_server.cpp 3 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | using boost::asio::ip::tcp; 22 | 23 | 24 | #include "SDL2/SDL.h" 25 | 26 | pthread_mutex_t bufferMutex; 27 | 28 | 29 | static const int32_t FRAMERATE = 30; 30 | #define SCREEN_WIDTH 320 31 | #define SCREEN_HEIGHT 256 32 | 33 | static int32_t sNow, sNext; 34 | 35 | SDL_Window *window = nullptr; 36 | SDL_Renderer *renderer = nullptr; 37 | SDL_Texture *texture = nullptr; 38 | 39 | 40 | const uint16_t matrixHeight = 64; 41 | const uint16_t matrixWidth = 64; 42 | const uint16_t matrixSize = (matrixHeight * matrixWidth); 43 | const uint16_t numMatricesWide = 1; 44 | const uint16_t numMatricesTall = 3; 45 | const uint16_t totalPixels = matrixSize * numMatricesWide * numMatricesTall; 46 | const size_t readBufferSize = totalPixels * sizeof(uint16_t); 47 | const size_t totalBytes = totalPixels * sizeof(uint16_t); 48 | 49 | uint16_t *screenBuffer; 50 | volatile bool interrupt_received = false; 51 | static void InterruptHandler(int signo) { 52 | interrupt_received = true; 53 | } 54 | 55 | void setupSDL() { 56 | 57 | printf("setupSDL\n"); 58 | // initialize any hardware 59 | SDL_Init(SDL_INIT_VIDEO); // Initialize SDL2 60 | 61 | uint32_t flags = SDL_WINDOW_OPENGL | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_RESIZABLE| SDL_WINDOW_SHOWN; 62 | 63 | // Create an application window with the following settings: 64 | window = SDL_CreateWindow( 65 | "genus", // window title 66 | SDL_WINDOWPOS_UNDEFINED, // initial resources position 67 | SDL_WINDOWPOS_UNDEFINED, // initial y position 68 | SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2, // width, in pixels 69 | flags // flags - see below 70 | ); 71 | 72 | SDL_SetWindowMinimumSize(window, SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2); 73 | 74 | if (window == nullptr) { 75 | printf("Could not create window: %s\n", SDL_GetError()); 76 | exit(1); 77 | } 78 | 79 | int w, h; 80 | SDL_GL_GetDrawableSize(window, &w, &h); 81 | printf("SDL window size %i x %i\n", w,h); 82 | 83 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); 84 | 85 | SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT); 86 | SDL_RenderSetIntegerScale(renderer, SDL_TRUE); 87 | 88 | texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, 89 | SCREEN_HEIGHT); 90 | 91 | if (!texture) { 92 | printf("Cannot create texture %s\n", SDL_GetError()); 93 | } 94 | 95 | 96 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 0); 97 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); 98 | SDL_RenderClear(renderer); 99 | SDL_GL_SetSwapInterval(1); 100 | 101 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); 102 | SDL_RenderClear(renderer); 103 | SDL_RenderPresent(renderer); 104 | 105 | { 106 | int x, y; 107 | SDL_GetWindowPosition(window, &x, &y); 108 | SDL_SetWindowPosition(window, x+1, y+1); 109 | } 110 | 111 | 112 | } 113 | 114 | bool shouldQuit = false; 115 | 116 | uint32_t toRGB888(uint16_t color) { 117 | 118 | uint8_t r = ((color >> 11) & 0x1F); 119 | uint8_t g = ((color >> 5) & 0x3F); 120 | uint8_t b = (color & 0x1F); 121 | 122 | r = ((((color >> 11) & 0x1F) * 527) + 23) >> 6; 123 | g = ((((color >> 5) & 0x3F) * 259) + 33) >> 6; 124 | b = (((color & 0x1F) * 527) + 23) >> 6; 125 | 126 | return r << 16 | g << 8 | b; 127 | 128 | } 129 | 130 | void startSDL2Window(void) { 131 | 132 | printf("renderWindow thread..."); 133 | 134 | uint32_t color = 0; 135 | while(! shouldQuit) { 136 | color++; 137 | SDL_Event e; 138 | if (SDL_PollEvent(&e)) { 139 | if (e.type == SDL_QUIT) { 140 | shouldQuit = true; 141 | break; 142 | } 143 | } 144 | 145 | int screenX, screenY; 146 | SDL_GetWindowPosition(window, &screenX, &screenY); 147 | 148 | void *screenBuf; 149 | int pitch; 150 | 151 | if (0 == SDL_LockTexture(texture, nullptr, &screenBuf, &pitch)) { 152 | auto *screenBits = (int32_t *) screenBuf; 153 | 154 | std::memset(screenBuf, color, (SCREEN_WIDTH * SCREEN_HEIGHT) * sizeof(int32_t)); 155 | 156 | pthread_mutex_lock(&bufferMutex); 157 | 158 | // Copy data to screenBuff 159 | 160 | // int sbIndex = 0; 161 | // for (int row = 0; row < matrixHeight * numMatricesTall; row++) { 162 | // for (int col = 0; col < matrixWidth * numMatricesWide; col++) { 163 | // *screenBits++ = toRGB888(screenBuffer[sbIndex]); 164 | // sbIndex++; 165 | // } 166 | // 167 | // screenBits += (SCREEN_WIDTH - (matrixWidth * numMatricesWide)); 168 | // } 169 | 170 | pthread_mutex_unlock(&bufferMutex); 171 | 172 | 173 | SDL_UnlockTexture(texture); 174 | } 175 | else { 176 | printf("Cannot lock texture!\n"); 177 | } 178 | 179 | 180 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); 181 | SDL_RenderClear(renderer); 182 | SDL_RenderCopy(renderer, texture, nullptr, nullptr); // Render texture to entire window 183 | SDL_RenderPresent(renderer); // Do update 184 | 185 | SDL_Delay(16); 186 | } 187 | } 188 | 189 | 190 | volatile uint32_t number_samples = 0; 191 | volatile double total_delta = 0; 192 | volatile double average; 193 | 194 | 195 | void receivePayload(tcp::socket sock) { 196 | 197 | // printf("receivePayload thread...\n"); 198 | 199 | int totalBytes = 0; 200 | int loopNum = 0; 201 | uint16_t sbIndex = 0; 202 | 203 | volatile clock_t start = 0; 204 | volatile clock_t end = 0; 205 | uint16_t data[readBufferSize]; 206 | 207 | try { 208 | while (! interrupt_received) { 209 | 210 | 211 | start = clock(); 212 | boost::system::error_code error; 213 | size_t length = boost::asio::read(sock, boost::asio::buffer(data), boost::asio::transfer_exactly(totalPixels), error); 214 | 215 | // printf("loopNum = %i, length = %lu\n", loopNum++, length); 216 | 217 | if (error == boost::asio::error::eof) { 218 | // printf("screenBuffer[0] == %i\n", screenBuffer[0]); 219 | // printf("Finished %i bytes\n---------------\n\n", totalBytes); 220 | break; // Connection closed cleanly by peer. 221 | } 222 | else if (error) { 223 | throw boost::system::system_error(error); // Some other error. 224 | } 225 | 226 | 227 | pthread_mutex_lock(&bufferMutex); 228 | totalBytes += length; 229 | 230 | for (int i = 0; i < totalPixels; i++) { 231 | screenBuffer[sbIndex++] = data[i]; 232 | } 233 | 234 | 235 | pthread_mutex_unlock(&bufferMutex); 236 | 237 | char *returnData = "K"; 238 | boost::asio::write(sock, boost::asio::buffer(returnData, 1)); 239 | 240 | if (sbIndex == totalPixels) { 241 | end = clock(); 242 | 243 | number_samples++; 244 | double delta = (end - start); 245 | // printf("start: %lu, end: %lu, delta = %2f\r", start, end, delta); 246 | total_delta += delta; 247 | 248 | average = total_delta / number_samples; 249 | 250 | // 251 | // printf("screenBuffer[0] == %i\n", screenBuffer[0]); 252 | // for (int i = 0; i < 3; i++) { 253 | // printf("screenBuffer[%i] == %i\n", i, screenBuffer[i]); fflush(stdout); 254 | // } 255 | // printf("Finished %lu bytes (BREAK!)\n---------------\n\n", totalPixels * sizeof(uint16_t)); 256 | // free(data); 257 | break; 258 | } 259 | 260 | } 261 | } 262 | catch (std::exception& e) { 263 | 264 | std::cerr << "Exception in thread: " << e.what() << "\n"; 265 | } 266 | } 267 | 268 | void server(boost::asio::io_context& io_context, unsigned short port) { 269 | tcp::acceptor a(io_context, tcp::endpoint(tcp::v4(), port)); 270 | 271 | while (! interrupt_received) { 272 | std::thread(receivePayload, a.accept()).detach(); 273 | } 274 | } 275 | 276 | void serverStarterThread(unsigned short port) { 277 | boost::asio::io_context io_context; 278 | 279 | // printf("serverThread %i\n", port); 280 | server(io_context, port); 281 | } 282 | 283 | volatile double priorAverage = 0; 284 | int retries = 0; 285 | void interrupterThread() { 286 | while(1) { 287 | if (interrupt_received) { 288 | exit(1); 289 | } 290 | printf("avg %f \r", average); 291 | 292 | if (average == priorAverage) { 293 | retries ++; 294 | if (retries > 5) { 295 | average = 0; 296 | retries = 0; 297 | } 298 | } 299 | priorAverage = average; 300 | 301 | 302 | usleep(10000); 303 | 304 | } 305 | 306 | 307 | } 308 | 309 | int main(int argc, char* argv[]) { 310 | 311 | 312 | std::cout << "\x1B[2J\x1B[H"; 313 | fflush(stdout); 314 | 315 | char hostname[1024]; 316 | hostname[1023] = '\0'; 317 | gethostname(hostname, 1023); 318 | 319 | printf("Server starting & expecting (%lu bytes)...\n", readBufferSize); fflush(stdout); 320 | printf("%s\n", hostname); 321 | fflush(stdout); 322 | 323 | signal(SIGTERM, InterruptHandler); 324 | signal(SIGINT, InterruptHandler); 325 | std::thread(interrupterThread).detach(); 326 | 327 | setupSDL(); 328 | 329 | screenBuffer = (uint16_t *)std::malloc(sizeof(uint16_t) * totalPixels); 330 | 331 | try { 332 | unsigned short port = 9890; 333 | for (int i = 0; i < 5; i++) { 334 | printf("Starting thread %i:\n", i); 335 | printf(" Listening on port %i\n", port); 336 | printf(" Attempting to receive %lu bytes\n", sizeof(uint16_t) * totalPixels); 337 | 338 | std::thread(serverStarterThread, port++).detach(); 339 | } 340 | std::thread(serverStarterThread, port).detach(); 341 | 342 | startSDL2Window(); 343 | 344 | } 345 | catch (std::exception& e) { 346 | std::cerr << "Exception: " << e.what() << "\n"; 347 | } 348 | 349 | 350 | 351 | int n = 0; 352 | // while(n < 5000) { 353 | // n++; 354 | // 355 | // SDL_Event e; 356 | // if (SDL_PollEvent(&e)) { 357 | // if (e.type == SDL_QUIT) { 358 | // break; 359 | // } 360 | // } 361 | // 362 | // int screenX, screenY; 363 | // SDL_GetWindowPosition(screen, &screenX, &screenY); 364 | // 365 | // void *screenBuf; 366 | // int pitch; 367 | // 368 | // if (0 == SDL_LockTexture(texture, nullptr, &screenBuf, &pitch)) { 369 | // auto *screenBits = (int32_t *) screenBuf; 370 | // 371 | // for (int y = 0; y < SCREEN_HEIGHT; y++) { 372 | // for (int x = 0; x < SCREEN_WIDTH; x++) { 373 | // *screenBits++ = (int32_t) (random() % INT32_MAX); 374 | // } 375 | // } 376 | // 377 | // 378 | // SDL_UnlockTexture(texture); 379 | // } 380 | // else { 381 | // printf("Cannot lock texture!\n"); 382 | // } 383 | // 384 | // 385 | // SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); 386 | // SDL_RenderClear(renderer); 387 | // SDL_RenderCopy(renderer, texture, nullptr, nullptr); // Render texture to entire window 388 | // SDL_RenderPresent(renderer); // Do update 389 | 390 | 391 | // SDL_Delay(10); 392 | // } 393 | 394 | while(! shouldQuit) { 395 | SDL_Delay(100); 396 | } 397 | 398 | 399 | 400 | printf("destroy SDL\n"); 401 | SDL_DestroyTexture(texture); 402 | SDL_DestroyRenderer(renderer); 403 | SDL_DestroyWindow(window); 404 | // Clean up 405 | SDL_Quit(); 406 | return 0; 407 | } 408 | -------------------------------------------------------------------------------- /sdl2-server-dummy/mkbuild.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # mkbuild.sh 4 | # 5 | # Wrapper script for running Terraform through Docker 6 | # 7 | # Useful when running in Jenkins CI or other contexts where you have Docker 8 | # available. 9 | 10 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 11 | set -euo pipefail 12 | IFS=$'\n\t' 13 | 14 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 15 | ${DEBUG:-false} && set -vx 16 | # Credit to https://stackoverflow.com/a/17805088 17 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 18 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 19 | 20 | # Credit to http://stackoverflow.com/a/246128/424301 21 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 22 | 23 | cd "$DIR" 24 | rm -rf build/ 25 | mkdir -p build/ 26 | 27 | cd build 28 | cmake .. 29 | make -j 4 30 | 31 | echo "next steps: cd build/" 32 | -------------------------------------------------------------------------------- /server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | #add_compile_definitions(BOOST_ASIO_ENABLE_OLD_SERVICES=true) 3 | 4 | project(matrix-server) 5 | 6 | set(CMAKE_CXX_STANDARD 14) 7 | 8 | set(CMAKE_BUILD_TYPE Debug) 9 | 10 | 11 | # external libraries 12 | INCLUDE(FindPkgConfig) 13 | 14 | FIND_PACKAGE(Boost REQUIRED) 15 | FIND_PACKAGE(Threads REQUIRED) 16 | 17 | set(INI_LIB_DIR ${CMAKE_SOURCE_DIR}/../lib/ini) 18 | 19 | 20 | INCLUDE_DIRECTORIES( 21 | ${Boost_INCLUDE_DIR} 22 | ${INI_LIB_DIR} 23 | ) 24 | 25 | add_compile_definitions(REMOVE_DEPRECATED_TRANSFORMERS true) 26 | 27 | #Flip the image vertically (Currently unused!) 28 | add_compile_definitions(__MATRIX_STRIP_BOTTOM_UP__ true) 29 | 30 | # Debug messages 31 | #add_compile_definitions(__MATRIX_SHOW_DEBUG_MESSAGES__ true) 32 | 33 | # Run as a daemon 34 | add_compile_definitions(__MATRIX_RUN_AS_DAEMON__ true) 35 | 36 | 37 | 38 | set(MATRIX_SRC_DIR ${CMAKE_SOURCE_DIR}/rpi-rgb-led-matrix/) 39 | 40 | # Currently using everything. Compilation is a little slower, we could work to remove items if we wish. 41 | file( 42 | GLOB LED_MATRIX_SRC 43 | ${CMAKE_SOURCE_DIR}/src/*.cpp 44 | ${MATRIX_SRC_DIR}/lib/*.cc 45 | ${MATRIX_SRC_DIR}/lib/*.c 46 | ${MATRIX_SRC_DIR}/lib/*.h 47 | ${INI_LIB_DIR}/*.c* 48 | ) 49 | 50 | INCLUDE_DIRECTORIES(${MATRIX_SRC_DIR}/include ${MATRIX_SRC_DIR}/lib) 51 | 52 | add_executable(${PROJECT_NAME} ${LED_MATRIX_SRC}) 53 | TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) 54 | 55 | add_custom_command( 56 | TARGET ${PROJECT_NAME} POST_BUILD 57 | COMMAND ${CMAKE_COMMAND} -E copy 58 | ${CMAKE_SOURCE_DIR}/rgb-server.ini 59 | ${CMAKE_CURRENT_BINARY_DIR}/rgb-server.ini) 60 | 61 | -------------------------------------------------------------------------------- /server/make-and-run.sh: -------------------------------------------------------------------------------- 1 | rm -rf build >/dev/null 2>&1 2 | mkdir build 3 | cd build 4 | cmake .. && make -j$(nproc) && ./matrix-server rgb-server.ini 5 | 6 | 7 | -------------------------------------------------------------------------------- /server/pidisplay.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Pi Display 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | Forking=true 8 | Restart=always 9 | ExecStart=/root/network-rgb-matrix-display/build/matrix-server /root/network-rgb-matrix-display/build/rgb-server.ini 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /server/rgb-server.ini: -------------------------------------------------------------------------------- 1 | ; Example INI file for a screen with 20 matrices 2 | ; broken up into 5 strips of 4 64x64 pixel screens 3 | [general] 4 | auto_clear_display = 1 ; 0 for static images 5 | auto_clear_delay = 500000 ; usleep() for this amount of time after we've hit a number of connection failures 6 | 7 | 8 | ; Single matrix dimensions 9 | [matrix_dimensions] 10 | width = 64 11 | height = 64 12 | 13 | [segment_info] 14 | panels_wide = 4 15 | panels_tall = 1 16 | 17 | [network] 18 | port = 9890 19 | ; Be sure to change this 20 | ;ip = 10.1.10.203 21 | ; and this for debugging purposes 22 | segment_id = 1 23 | 24 | 25 | ; These are passed on to the rpi-rgb-led-matrix library 26 | [rgb_matrix_lib_options] 27 | ; Name of the hardware mapping. Something like "regular" or "adafruit-hat" 28 | hardware_mapping = regular 29 | 30 | ; The number of parallel chains connected to the Pi; in old Pis with 26 31 | ; GPIO pins, that is 1, in newer Pis with 40 interfaces pins, that can 32 | ; also be 2 or 3. The effective number of pixels in vertical direction is 33 | ; then thus rows * parallel. Default: 1 34 | parallel = 1 35 | 36 | 37 | ; Set PWM bits used for output. Default is 11, but if you only deal with 38 | ; limited comic-colors, 1 might be sufficient. Lower require less CPU and 39 | ; increases refresh-rate. 40 | ; RPi4 - set to 6. Rpi 3, set to 4. 41 | pwm_bits = 6 42 | 43 | ; Change the base time-unit for the on-time in the lowest 44 | ; significant bit in nanoseconds. 45 | ; Higher numbers provide better quality (more accurate color, less 46 | ; ghosting), but have a negative impact on the frame rate. 47 | ; range of pwm-lsb-nanoseconds (50..3000 allowed). 48 | ; 130 is default 49 | pwm_lsb_nanoseconds = 302 50 | 51 | ; The lower bits can be time-dithered for higher refresh rate. 52 | ; Flag: --led-pwm-dither-bits 53 | pwm_dither_bits = 1 54 | 55 | ; The initial brightness of the panel in percent. Valid range is 1..100 56 | ; Default: 100 57 | brightness = 100 58 | 59 | ; Scan mode: 0=progressive, 1=interlaced 60 | scan_mode = 0 61 | 62 | ; Default row address type is 0, corresponding to direct setting of the 63 | ; row, while row address type 1 is used for panels that only have A/B, 64 | ; typically some 64x64 panels 65 | row_address_type = 0 66 | 67 | ; Type of multiplexing. 0 = direct, 1 = stripe, 2 = checker (typical 1:8) 68 | multiplexing = 0 69 | 70 | ; Disable the PWM hardware subsystem to create pulses. 71 | ; Typically, you don't want to disable hardware pulsing, this is mostly 72 | ; for debugging and figuring out if there is interference with the 73 | ; sound system. 74 | ; This won't do anything if output enable is not connected to GPIO 18 in 75 | ; non-standard wirings. 76 | ; 0 false, 1 true 77 | disable_hardware_pulsing = 0 78 | 79 | ; 0 false, 1 true 80 | show_refresh_rate = 0 81 | ; 0 false, 1 true 82 | inverse_colors = 0 83 | 84 | ; In case the internal sequence of mapping is not "RGB", this contains the 85 | ; real mapping. Some panels mix up these colors. 86 | ; see led-matrix.h for more details 87 | led_rgb_sequence = RGB 88 | 89 | ; A string describing a sequence of pixel mappers that should be applied 90 | ; to this matrix. A semicolon-separated list of pixel-mappers with optional 91 | ; parameter. 92 | 93 | pixel_mapper_config = Rotate:0 94 | 95 | [rgb_matrix_runtime_options] 96 | ; 0 = no slowdown. 97 | gpio_slowdown = 3 98 | 99 | ; ---------- 100 | ; If the following options are set to disabled with -1, they are not 101 | ; even offered via the command line flags. 102 | ; ---------- 103 | 104 | ; If daemon is disabled (= -1), the user has to call StartRefresh() manually 105 | ; once the matrix is created, to leave the decision to become a daemon 106 | ; after the call (which requires that no threads have been started yet). 107 | ; In the other cases (off or on), the choice is already made, so the thread 108 | ; is conveniently already started for you. 109 | ; -1 disabled. 0=off, 1=on. 110 | daemon = 1 111 | 112 | ; Drop privileges from 'root' to 'daemon' once the hardware is initialized. 113 | ; This is usually a good idea unless you need to stay on elevated privs. 114 | ; -1 disabled. 0=off, 1=on. 115 | drop_privileges = -1 116 | 117 | ; By default, the gpio is initialized for you, but if you want to manually 118 | ; do that yourself, set this flag to false. 119 | ; Then, you have to initialize the matrix yourself with SetGPIO(). 120 | ; Use 1 for true, 0 for false 121 | do_gpio_init = 1 -------------------------------------------------------------------------------- /server/src/MatrixSegment.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This class is used to Paint the RGB Matrix and is meant to be as efficient as possible. 4 | * All color processing should be done via the NetworkServer class. 5 | */ 6 | 7 | #include "MatrixSegment.h" 8 | #include "unistd.h" 9 | 10 | MatrixSegment::MatrixSegment(RGBMatrix *m) : ThreadedCanvasManipulator(m), mMatrix(m) { 11 | #ifdef __linux__ 12 | pthread_mutex_destroy(&mMutex); 13 | pthread_mutex_init(&mMutex, nullptr); 14 | #endif 15 | 16 | mShouldRun = true; 17 | 18 | mCanvas1 = m->CreateFrameCanvas(); 19 | mRenderCanvas = mCanvas1; 20 | 21 | mCanvas2 = m->CreateFrameCanvas(); 22 | mDisplayCanvas = mCanvas2; 23 | 24 | mCanvasWidth = mRenderCanvas->width(); 25 | mCanvasHeight = mRenderCanvas->height(); 26 | mFrameCount = 0; 27 | 28 | 29 | // Describe(); 30 | 31 | 32 | #ifdef __MATRIX_SHOW_DEBUG_MESSAGES__ 33 | Describe(); 34 | #endif 35 | } 36 | static uint16_t msColor = 0; 37 | 38 | void MatrixSegment::Run() { 39 | 40 | volatile uint32_t currentFrameCount = 0; 41 | 42 | while (running() && mShouldRun) { 43 | if (mFrameCount != currentFrameCount) { 44 | 45 | // msColor++; 46 | // uint8_t r = (msColor & 0xF800) >> 8; // rrrrr... ........ -> rrrrr000 47 | // uint8_t g = (msColor & 0x07E0) >> 3; // .....ggg ggg..... -> gggggg00 48 | // uint8_t b = (msColor & 0x1F) << 3; // ............bbbbb -> bbbbb000 49 | // mRenderCanvas->Fill(r,g,b); 50 | 51 | #ifdef __MATRIX_SHOW_DEBUG_MESSAGES__ 52 | printf("mDisplayCanvas height %i :: width %i\n", mRenderCanvas->height(), mRenderCanvas->width()); 53 | 54 | if (mRenderCanvas == mCanvas1) { 55 | printf("mRenderCanvas = mCanvas 1 :: %p !\n", mCanvas1);fflush(stdout); 56 | } 57 | else { 58 | printf("mRenderCanvas = mCanvas 2 :: %p !\n", mCanvas2);fflush(stdout); 59 | } 60 | #endif 61 | SwapBuffers(); 62 | mDisplayCanvas = mMatrix->SwapOnVSync(mDisplayCanvas, 1); 63 | currentFrameCount = mFrameCount; 64 | } 65 | // else { 66 | // usleep(10); 67 | // } 68 | 69 | } 70 | 71 | printf("MatrixStrip::Run() ended!!\n"); 72 | } 73 | 74 | // Used for debug purposes only. 75 | void MatrixSegment::Describe() { 76 | printf("MatrixStrip %p\n", this); 77 | 78 | // printf("\tmTotalPixels = %lu\n", mTotalPixels); 79 | printf("\tmCanvasWidth = %i\n", mCanvasWidth); 80 | printf("\tmCanvasHeight = %i\n", mCanvasHeight); 81 | printf("\tmShouldClearBuffers = %i\n", mShouldClearBuffers); 82 | printf("\tmClearBuffersDelay = %i\n", mClearBuffersDelay); 83 | } -------------------------------------------------------------------------------- /server/src/MatrixSegment.h: -------------------------------------------------------------------------------- 1 | #ifndef MATRIX_MATRIXSTRIP_H 2 | #define MATRIX_MATRIXSTRIP_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "led-matrix.h" 9 | #include "threaded-canvas-manipulator.h" 10 | #include "pixel-mapper.h" 11 | #include "graphics.h" 12 | 13 | using namespace rgb_matrix; 14 | 15 | 16 | class MatrixSegment : public ThreadedCanvasManipulator { 17 | 18 | public: 19 | explicit MatrixSegment(RGBMatrix *m); 20 | 21 | void Run() override; 22 | 23 | size_t mTotalPixels; 24 | uint16_t mCanvasWidth; 25 | uint16_t mCanvasHeight; 26 | volatile uint16_t mFrameCount; 27 | 28 | FrameCanvas *mRenderCanvas; 29 | FrameCanvas *mDisplayCanvas; 30 | volatile bool mShouldRun; 31 | 32 | bool mShouldClearBuffers; 33 | unsigned long mClearBuffersDelay; 34 | 35 | 36 | public: 37 | void SwapBuffers() { 38 | LockMutex(); 39 | 40 | if (mRenderCanvas == mCanvas1) { 41 | mRenderCanvas = mCanvas2; 42 | mDisplayCanvas = mCanvas1; 43 | } 44 | else { 45 | mRenderCanvas = mCanvas1; 46 | mDisplayCanvas = mCanvas2; 47 | } 48 | 49 | UnlockMutex(); 50 | } 51 | 52 | void LockMutex() { 53 | // printf("MatrixStrip::%s\n", __FUNCTION__); fflush(stdout); 54 | pthread_mutex_lock(&mMutex); 55 | } 56 | 57 | void UnlockMutex() { 58 | // printf("MatrixStrip::%s\n", __FUNCTION__); fflush(stdout); 59 | pthread_mutex_unlock(&mMutex); 60 | } 61 | 62 | 63 | void ClearBuffers() { 64 | // printf("\n%s %i\n", __FUNCTION__, mFrameCount); fflush(stdout); 65 | 66 | LockMutex(); 67 | mCanvas1->Fill(0,0,0); 68 | mCanvas2->Fill(0,0,0); 69 | UnlockMutex(); 70 | mFrameCount++; 71 | } 72 | 73 | FrameCanvas *GetRenderCanvas() { 74 | return mRenderCanvas; 75 | } 76 | 77 | void Describe(); 78 | 79 | private: 80 | FrameCanvas *mCanvas1; 81 | FrameCanvas *mCanvas2; 82 | 83 | 84 | RGBMatrix *const mMatrix; 85 | pthread_mutex_t mMutex; 86 | }; 87 | 88 | 89 | 90 | 91 | 92 | #endif //MATRIX_MATRIXSTRIP_H 93 | -------------------------------------------------------------------------------- /server/src/NetworkServer.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This class is designed to only run on the RaspberryPi and setup an unencrypted TCP/IP server 4 | * on port 9890. It recieves screen buffer data and then copies it to the next available 5 | * buffer on the MatrixStrip instance after shifting the inbound 16bit color to the desired 6 | * 32bit color scheme. 7 | * 8 | * This thread is also only to run on CPU 3, with a 99 priority. 9 | */ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "NetworkServer.h" 20 | #include "NetworkServerConfig.h" 21 | 22 | using boost::asio::ip::tcp; 23 | using std::min; 24 | using std::max; 25 | 26 | NetworkServer::NetworkServer(NetworkServerConfig *config) { 27 | //#ifdef __linux__ 28 | pthread_mutex_destroy(&mMutex); 29 | pthread_mutex_init(&mMutex, nullptr); 30 | //#endif 31 | 32 | mNumberSamples = 0; 33 | mTotalDelta = 0; 34 | mAverage = 0; 35 | mPriorAverage = 0; 36 | mThreadRunning = false; 37 | 38 | mFrameCount = 0; 39 | mSegmentId = config->segmentId; 40 | mIncomingPort = config->incomingPort; 41 | 42 | mSinglePanelWidth = config->singlePanelWidth; 43 | mSinglePanelHeight = config->singlePanelHeight; 44 | 45 | mPixelsPerPanel = mSinglePanelWidth * mSinglePanelWidth; 46 | 47 | mSegmentWidth = config->singlePanelWidth * config->numPanelsWide; 48 | mSegmentHeight = config->singlePanelHeight * config->numPanelsTall; 49 | 50 | mPanelsWide = config->numPanelsWide; 51 | mPanelsTall = config->numPanelsTall; 52 | 53 | mTotalPixels = mPixelsPerPanel * mPanelsWide * mPanelsTall; 54 | mTotalBytes = mTotalPixels * sizeof(uint16_t); 55 | 56 | mMatrixStrip = config->matrixStripInstance; 57 | 58 | mPort = config->port; 59 | fflush(stdout); 60 | mIP = strdup(config->ip); 61 | 62 | 63 | #ifdef __MATRIX_SHOW_DEBUG_MESSAGES__ 64 | Describe(); 65 | #endif 66 | } 67 | 68 | uint16_t nColor = 0; 69 | int nX = 0; 70 | int nY = 0; 71 | 72 | void NetworkServer::ReceiveDataThread(tcp::socket sock) { 73 | int numBytesReceived = 0; 74 | uint16_t sbIndex = 0; 75 | 76 | volatile clock_t start = 0; 77 | volatile clock_t end = 0; 78 | 79 | // 32768 = ((NumPixelsWide * NmPixelsTall) * NumPixelsInSegment) * SizeOf(uint16_t) 80 | // = ((64 * 64) * 4) * 2 81 | // static uint16_t data[32768]; 82 | // memset(data,0,mTotalBytes); 83 | auto *data = (uint16_t *)malloc(mTotalBytes); 84 | bzero(data, mTotalBytes); 85 | const char *returnData = "K"; 86 | 87 | while (GetThreadRunning()) { 88 | nColor = random() & UINT16_MAX; 89 | 90 | try { 91 | nColor++; 92 | start = clock(); 93 | 94 | boost::system::error_code error; 95 | size_t length = boost::asio::read(sock, boost::asio::buffer(data, mTotalBytes), boost::asio::transfer_exactly(mTotalBytes), error); 96 | 97 | numBytesReceived += length; 98 | // printf("numBytesReceived %i\n", numBytesReceived); 99 | // Ended early! No bueno! 100 | if (error == boost::asio::error::eof){ 101 | printf("Eof\n"); fflush(stdout); 102 | break; 103 | } 104 | else if (error) { 105 | throw boost::system::system_error(error); // Some other error. 106 | } 107 | 108 | 109 | boost::asio::write(sock, boost::asio::buffer(returnData, 1)); 110 | 111 | 112 | 113 | 114 | // this thing re-orients the data so that the pixels are mapped vertically 115 | if (numBytesReceived == mTotalBytes) { 116 | end = clock(); 117 | mNumberSamples++; 118 | double delta = (end - start); 119 | mTotalDelta += delta; 120 | mAverage = (mTotalDelta / mNumberSamples) ; 121 | 122 | // This is some debugging crap. 123 | // uint8_t r = (nColor & 0xF800) >> 8; // rrrrr... ........ -> rrrrr000 124 | // uint8_t g = (nColor & 0x07E0) >> 3; // .....ggg ggg..... -> gggggg00 125 | // uint8_t b = (nColor & 0x1F) << 3; // ............bbbbb -> bbbbb000 126 | // printf("Here\n"); 127 | // mMatrixStrip->GetRenderCanvas()->Fill(r,g,b); 128 | // mMatrixStrip->mFrameCount++; 129 | // 130 | 131 | 132 | // 133 | // int ptrIndex = mTotalPixels - 1; 134 | // uint16_t *outputBuff = data; 135 | // 136 | // int y = 0; 137 | // int x = mMatrixStrip->mCanvasWidth - 1; 138 | // for (; x > -1 ; x--) { 139 | //// printf("x %i\n", x); 140 | // y = 0; 141 | // for (; y < mMatrixStrip->mCanvasHeight; y++) { 142 | // 143 | // uint16_t pixel = outputBuff[ptrIndex--]; 144 | // // Color separation based off : https://stackoverflow.com/questions/38557734/how-to-convert-16-bit-hex-color-to-rgb888-values-in-c 145 | // uint8_t r = (pixel & 0xF800) >> 8; // rrrrr... ........ -> rrrrr000 146 | // uint8_t g = (pixel & 0x07E0) >> 3; // .....ggg ggg..... -> gggggg00 147 | // uint8_t b = (pixel & 0x1F) << 3; // ............bbbbb -> bbbbb000 148 | // 149 | // mMatrixStrip->GetRenderCanvas()->SetPixel(x, y, r, g, b); 150 | // } 151 | // 152 | // } 153 | 154 | const int width = mMatrixStrip->GetRenderCanvas()->width(); 155 | const int height = mMatrixStrip->GetRenderCanvas()->height(); 156 | uint16_t *outputBuff = data; 157 | int ptrIndex = 0; 158 | int y = 0; 159 | for (; y < height; y++) { 160 | 161 | for (int x = 0; x < width ; x++) { 162 | uint16_t pixel = outputBuff[ptrIndex++]; 163 | // Color separation based off : https://stackoverflow.com/questions/38557734/how-to-convert-16-bit-hex-color-to-rgb888-values-in-c 164 | uint8_t r = (pixel & 0xF800) >> 8; // rrrrr... ........ -> rrrrr000 165 | uint8_t g = (pixel & 0x07E0) >> 3; // .....ggg ggg..... -> gggggg00 166 | uint8_t b = (pixel & 0x1F) << 3; // ............bbbbb -> bbbbb000 167 | 168 | mMatrixStrip->GetRenderCanvas()->SetPixel(x, y, r, g, b); 169 | } 170 | } 171 | 172 | mMatrixStrip->mFrameCount++; 173 | break; 174 | } 175 | 176 | } 177 | catch (std::exception& e) { 178 | std::cerr << __FUNCTION__ << " Exception: " << e.what() << "\n"; 179 | } 180 | } 181 | 182 | delete data; 183 | mFrameCount++; 184 | } 185 | 186 | 187 | void NetworkServer::ServerStartingThread() { 188 | mThreadRunning = true; 189 | 190 | boost::asio::io_context io_context; 191 | unsigned short port = mPort; 192 | tcp::acceptor a(io_context, tcp::endpoint(tcp::v4(), port)); 193 | 194 | sched_param sch_params; 195 | sch_params.sched_priority = 99; 196 | 197 | int affinity_mask = (1<<2); 198 | 199 | cpu_set_t cpu_mask; 200 | CPU_ZERO(&cpu_mask); 201 | 202 | for (int i = 0; i < 32; ++i) { 203 | if ((affinity_mask & (1< 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | using boost::asio::ip::tcp; 15 | 16 | 17 | #include "MatrixSegment.h" 18 | #include "NetworkServerConfig.h" 19 | #include "ini.h" 20 | 21 | class NetworkServer { 22 | 23 | public: 24 | int mSegmentId; 25 | int mSinglePanelWidth; 26 | int mSinglePanelHeight; 27 | int mPixelsPerPanel; 28 | 29 | int mPanelsWide; 30 | int mPanelsTall; 31 | 32 | int mSegmentWidth; 33 | int mSegmentHeight; 34 | 35 | int mTotalPixels; 36 | 37 | size_t mTotalBytes; 38 | 39 | unsigned short mIncomingPort; 40 | 41 | volatile uint32_t mNumberSamples; 42 | volatile double mTotalDelta; 43 | volatile double mAverage; 44 | volatile double mPriorAverage; 45 | 46 | public: 47 | explicit NetworkServer(NetworkServerConfig *config); 48 | 49 | ~NetworkServer(); 50 | 51 | void ReceiveDataThread(tcp::socket sock); 52 | void ServerStartingThread(); 53 | 54 | void LockMutex(); 55 | void UnlockMutex(); 56 | 57 | void ClearBuffers() { 58 | printf("\n%s\n", __FUNCTION__); 59 | fflush(stdout); 60 | LockMutex(); 61 | mMatrixStrip->mRenderCanvas->Fill(0, 0, 0); 62 | mMatrixStrip->mDisplayCanvas->Fill(0, 0, 0); 63 | mFrameCount++; 64 | UnlockMutex(); 65 | } 66 | 67 | bool GetThreadRunning() { 68 | return mThreadRunning; 69 | } 70 | 71 | void StartThread(); 72 | void StopThread(); 73 | 74 | void Describe(); 75 | 76 | uint16_t GetFrameCount() { 77 | return mFrameCount; 78 | } 79 | 80 | void IncrementFrameCount() { 81 | mFrameCount++; 82 | } 83 | private: 84 | volatile uint16_t mFrameCount; 85 | volatile bool mThreadRunning; 86 | 87 | pthread_mutex_t mMutex; 88 | std::thread mThread; 89 | unsigned short mPort; 90 | const char *mIP; 91 | 92 | MatrixSegment *mMatrixStrip; 93 | }; 94 | 95 | 96 | 97 | 98 | #endif //MATRIX_NETWORKSERVER_H 99 | -------------------------------------------------------------------------------- /server/src/NetworkServerConfig.h: -------------------------------------------------------------------------------- 1 | #ifndef MATRIX_SERVER_NETWORKSERVERCONFIG_H 2 | #define MATRIX_SERVER_NETWORKSERVERCONFIG_H 3 | 4 | 5 | 6 | #include "MatrixSegment.h" 7 | #include "INIReader.h" 8 | #include "led-matrix.h" 9 | 10 | 11 | 12 | class NetworkServerConfig { 13 | public: 14 | static NetworkServerConfig *FromIniFile(const char *aFile) { 15 | auto *serverConfig = new NetworkServerConfig(); 16 | 17 | auto *reader = new INIReader(aFile); 18 | 19 | serverConfig->autoClearDisplay = (bool)reader->GetBoolean("general", "auto_clear_display", true); 20 | serverConfig->autoClearDelay = (unsigned long)reader->GetInteger("general", "auto_clear_delay", 50000000); 21 | 22 | 23 | serverConfig->singlePanelWidth = (int)reader->GetInteger("matrix_dimensions", "width", 0); 24 | serverConfig->singlePanelHeight = (int)reader->GetInteger("matrix_dimensions", "height", 0); 25 | serverConfig->numPanelsWide = (int)reader->GetInteger("segment_info", "panels_wide", 0); 26 | serverConfig->numPanelsTall = (int)reader->GetInteger("segment_info", "panels_tall", 0); 27 | // printf("ip %s\n", reader->GetString("network", "ip", "").c_str()); 28 | // fflush(stdout); 29 | 30 | serverConfig->ip = strdup(reader->GetString("network", "ip", "").c_str()); 31 | serverConfig->port = (unsigned short)reader->GetInteger("network", "port", 0); 32 | serverConfig->incomingPort = (int)reader->GetInteger("network", "port", 0); 33 | 34 | const char *libOptions = "rgb_matrix_lib_options"; 35 | 36 | serverConfig->matrix_options.hardware_mapping = strdup(reader->GetString(libOptions, "hardware_mapping", "").c_str()); 37 | serverConfig->matrix_options.parallel = (int)reader->GetInteger(libOptions, "parallel", 1); 38 | // serverConfig->matrix_options.chain_length = (int)reader->GetInteger("matrix_dimensions", "chain_length", 1); 39 | 40 | serverConfig->matrix_options.chain_length = serverConfig->numPanelsWide > serverConfig->numPanelsTall ? serverConfig->numPanelsWide : serverConfig->numPanelsTall; 41 | 42 | serverConfig->matrix_options.pwm_bits = (int)reader->GetInteger(libOptions, "pwm_bits", 0); 43 | serverConfig->matrix_options.pwm_lsb_nanoseconds = (int)reader->GetInteger(libOptions, "pwm_lsb_nanoseconds", 0); 44 | serverConfig->matrix_options.pwm_dither_bits = (int)reader->GetInteger(libOptions, "pwm_dither_bits", 0); 45 | serverConfig->matrix_options.brightness = (int)reader->GetInteger(libOptions, "brightness", 100); 46 | serverConfig->matrix_options.scan_mode = (int)reader->GetInteger(libOptions, "scan_mode", 0); 47 | serverConfig->matrix_options.multiplexing = (int)reader->GetInteger(libOptions, "multiplexing", 0); 48 | serverConfig->matrix_options.disable_hardware_pulsing = (bool)reader->GetBoolean(libOptions, "disable_hardware_pulsing", false); 49 | serverConfig->matrix_options.show_refresh_rate = (bool)reader->GetInteger(libOptions, "show_refresh_rate", false); 50 | serverConfig->matrix_options.inverse_colors = (bool)reader->GetInteger(libOptions, "inverse_colors", false); 51 | serverConfig->matrix_options.led_rgb_sequence = strdup(reader->GetString(libOptions, "led_rgb_sequence", "RGB").c_str()); 52 | serverConfig->matrix_options.pixel_mapper_config = strdup(reader->GetString(libOptions, "pixel_mapper_config", "").c_str()); 53 | 54 | const char *runtimeOptions = "rgb_matrix_runtime_options"; 55 | serverConfig->runtime_options.gpio_slowdown = (int)reader->GetInteger(runtimeOptions, "gpio_slowdown", 0); 56 | serverConfig->runtime_options.daemon = (int)reader->GetInteger(runtimeOptions, "daemon", -1); 57 | serverConfig->runtime_options.drop_privileges = (int)reader->GetInteger(runtimeOptions, "drop_privileges", -1); 58 | serverConfig->runtime_options.do_gpio_init = (bool)reader->GetBoolean(runtimeOptions, "do_gpio_init", true); 59 | 60 | 61 | return serverConfig; 62 | } 63 | public: 64 | NetworkServerConfig() { 65 | 66 | singlePanelWidth = 0; 67 | singlePanelHeight = 0; 68 | 69 | numPanelsWide = 0; 70 | numPanelsTall = 0; 71 | segmentId = 0; 72 | incomingPort = 0; 73 | // totalSinglePanelSize = 0; 74 | totalPixels = 0; 75 | port = 0; 76 | ip = nullptr; 77 | matrixStripInstance = nullptr; 78 | 79 | }; 80 | ~NetworkServerConfig() = default; 81 | 82 | int singlePanelWidth; 83 | int singlePanelHeight; 84 | int numPanelsWide; 85 | int numPanelsTall; 86 | int segmentId; 87 | int incomingPort; 88 | 89 | bool autoClearDisplay; 90 | unsigned long autoClearDelay; 91 | 92 | // int totalSinglePanelSize; 93 | int totalPixels; 94 | unsigned short port; 95 | const char *ip; 96 | MatrixSegment *matrixStripInstance; 97 | 98 | RGBMatrix::Options matrix_options; 99 | rgb_matrix::RuntimeOptions runtime_options; 100 | 101 | void Describe() { 102 | printf("------------\n"); 103 | printf("NetworkServerConfig:\n"); 104 | printf("\tsinglePanelWidth = %i\n", singlePanelWidth); 105 | printf("\tsinglePanelHeight = %i\n", singlePanelHeight); 106 | printf("\tnumPanelsWide = %i\n", numPanelsWide); 107 | printf("\tnumPanelsTall = %i\n", numPanelsTall); 108 | printf("\tsegmentId = %i\n", segmentId); 109 | printf("\tincomingPort = %i\n", incomingPort); 110 | // printf("\ttotalSinglePanelSize = %i\n", totalSinglePanelSize); 111 | printf("\ttotalPixels = %i\n", totalPixels); 112 | printf("\tautoClearDisplay = %s\n", autoClearDisplay ? "true" : "false"); 113 | printf("\tautoClearDelay = %lu\n", autoClearDelay); 114 | printf("\tip = %s\n", ip); 115 | printf("\tport = %u\n", port); 116 | printf("--- matrix_options ---\n"); 117 | printf("\tmatrix_options.parallel = %i\n", matrix_options.parallel); 118 | printf("\tmatrix_options.chain_length = %i\n", matrix_options.chain_length); 119 | printf("\tmatrix_options.pwm_bits = %i\n", matrix_options.pwm_bits); 120 | printf("\tmatrix_options.pwm_lsb_nanoseconds = %i\n", matrix_options.pwm_lsb_nanoseconds); 121 | printf("\tmatrix_options.pwm_dither_bits = %i\n", matrix_options.pwm_dither_bits); 122 | printf("\tmatrix_options.brightness = %i\n", matrix_options.brightness); 123 | printf("\tmatrix_options.scan_mode = %i\n", matrix_options.scan_mode); 124 | printf("\tmatrix_options.row_address_type = %i\n", matrix_options.row_address_type); 125 | printf("\tmatrix_options.multiplexing = %i\n", matrix_options.multiplexing); 126 | printf("\tmatrix_options.disable_hardware_pulsing = %i\n", matrix_options.disable_hardware_pulsing); 127 | printf("\tmatrix_options.show_refresh_rate = %i\n", matrix_options.show_refresh_rate); 128 | printf("\tmatrix_options.inverse_colors = %i\n", matrix_options.inverse_colors); 129 | printf("--- runtime_options ---\n"); 130 | printf("\truntime_options.gpio_slowdown = %i\n", runtime_options.gpio_slowdown); 131 | printf("\truntime_options.daemon = %i\n", runtime_options.daemon); 132 | printf("\truntime_options.drop_privileges = %i\n", runtime_options.drop_privileges); 133 | printf("\truntime_options.do_gpio_init = %i\n", runtime_options.do_gpio_init); 134 | fflush(stdout); 135 | 136 | } 137 | 138 | }; 139 | 140 | 141 | 142 | #endif //MATRIX_SERVER_NETWORKSERVERCONFIG_H 143 | -------------------------------------------------------------------------------- /server/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "led-matrix.h" 8 | #include "graphics.h" 9 | 10 | #include 11 | #include 12 | 13 | #include "NetworkServer.h" 14 | #include "NetworkServerConfig.h" 15 | #include "MatrixSegment.h" 16 | #include "ini.h" 17 | 18 | using std::min; 19 | using std::max; 20 | using boost::asio::ip::tcp; 21 | using namespace rgb_matrix; 22 | 23 | 24 | pthread_mutex_t bufferMutex; 25 | 26 | 27 | /***********************************************************************/ 28 | 29 | volatile bool interrupt_received = false; 30 | static void InterruptHandler(int signo) { 31 | interrupt_received = true; 32 | } 33 | 34 | NetworkServer *server; 35 | double priorAverage = 0; 36 | 37 | int retries = 0; 38 | 39 | bool shouldQuit = false; 40 | MatrixSegment *matrixStrip = nullptr; 41 | 42 | // This function will loop and help exit gracefully if an interrupt is received. 43 | void interrupterThread() { 44 | 45 | while(1) { 46 | if (interrupt_received) { 47 | server->StopThread(); 48 | exit(1); 49 | } 50 | 51 | 52 | if (server->mAverage == priorAverage) { 53 | retries++; 54 | if (retries > 4) { 55 | // printf("\n**CLEAR BUFFERS!! retries=%i\n",retries); 56 | 57 | server->mAverage = 0; 58 | retries = 0; 59 | if (matrixStrip && matrixStrip->mShouldClearBuffers == true) { 60 | matrixStrip->ClearBuffers(); 61 | } 62 | } 63 | 64 | } 65 | 66 | priorAverage = server->mAverage; 67 | usleep(matrixStrip->mClearBuffersDelay); 68 | } 69 | } 70 | 71 | /***********************************************************************/ 72 | 73 | 74 | void start_matrix(NetworkServerConfig *aServerConfig) { 75 | 76 | RGBMatrix::Options matrix_options; 77 | 78 | 79 | matrix_options.chain_length = aServerConfig->numPanelsWide; 80 | matrix_options.cols = aServerConfig->singlePanelWidth; 81 | matrix_options.rows = aServerConfig->singlePanelHeight; 82 | 83 | matrix_options.hardware_mapping = strdup(aServerConfig->matrix_options.hardware_mapping); 84 | matrix_options.chain_length = aServerConfig->matrix_options.chain_length; 85 | matrix_options.parallel = aServerConfig->matrix_options.parallel; 86 | matrix_options.pwm_bits = aServerConfig->matrix_options.pwm_bits; 87 | matrix_options.pwm_lsb_nanoseconds = aServerConfig->matrix_options.pwm_lsb_nanoseconds; 88 | matrix_options.pwm_dither_bits = aServerConfig->matrix_options.pwm_dither_bits; 89 | matrix_options.brightness = aServerConfig->matrix_options.brightness; 90 | matrix_options.scan_mode = aServerConfig->matrix_options.scan_mode; 91 | matrix_options.multiplexing = aServerConfig->matrix_options.multiplexing; 92 | matrix_options.disable_hardware_pulsing = aServerConfig->matrix_options.disable_hardware_pulsing; 93 | matrix_options.row_address_type = aServerConfig->matrix_options.row_address_type; 94 | 95 | matrix_options.show_refresh_rate = aServerConfig->matrix_options.show_refresh_rate; 96 | matrix_options.inverse_colors = aServerConfig->matrix_options.inverse_colors; 97 | matrix_options.led_rgb_sequence = strdup(aServerConfig->matrix_options.led_rgb_sequence); 98 | 99 | matrix_options.pixel_mapper_config = strdup(aServerConfig->matrix_options.pixel_mapper_config); 100 | 101 | 102 | rgb_matrix::RuntimeOptions runtime_opt; 103 | runtime_opt.gpio_slowdown = aServerConfig->runtime_options.gpio_slowdown; 104 | // runtime_opt.daemon = aServerConfig->runtime_options.daemon; 105 | // runtime_opt.drop_privileges = aServerConfig->runtime_options.drop_privileges; 106 | // runtime_opt.do_gpio_init = aServerConfig->runtime_options.do_gpio_init; 107 | 108 | RGBMatrix *matrix = CreateMatrixFromOptions(matrix_options, runtime_opt); 109 | 110 | if (matrix == nullptr) { 111 | printf("ERROR! Could not create RGBMatrix instance!!!!\n"); 112 | exit(1); 113 | } 114 | 115 | printf("Size: %dx%d. Hardware gpio mapping: %s\n", 116 | matrix->width(), matrix->height(), matrix_options.hardware_mapping); 117 | 118 | matrixStrip = new MatrixSegment(matrix); 119 | matrixStrip->mTotalPixels = aServerConfig->totalPixels; 120 | matrixStrip->mShouldClearBuffers = aServerConfig->autoClearDisplay; 121 | 122 | matrixStrip->mClearBuffersDelay = (unsigned long)aServerConfig->autoClearDelay; 123 | 124 | printf("matrixStrip->Start()\n"); fflush(stdout); 125 | matrixStrip->Start(); 126 | } 127 | 128 | 129 | int main(int argc, char* argv[]) { 130 | signal(SIGTERM, InterruptHandler); 131 | signal(SIGINT, InterruptHandler); 132 | char hostname[1024]; 133 | hostname[1023] = '\0'; 134 | gethostname(hostname, 1023); 135 | std::cout << "\x1B[2J\x1B[H"; 136 | printf("%s\n", hostname); 137 | fflush(stdout); 138 | 139 | if (argc < 2) { 140 | fprintf(stderr, "Fatal Error! Please specify INI file to open.\n\n"); 141 | exit(127); 142 | } 143 | 144 | // const char *file = "rgb-server.ini"; 145 | NetworkServerConfig *serverConfig = NetworkServerConfig::FromIniFile(argv[1]); 146 | serverConfig->Describe(); 147 | printf("serverConfig %p\n", serverConfig); fflush(stdout); 148 | start_matrix(serverConfig); 149 | serverConfig->matrixStripInstance = matrixStrip; 150 | 151 | server = new NetworkServer(serverConfig); 152 | server->Describe(); 153 | server->StartThread(); 154 | 155 | std::thread(interrupterThread).detach(); 156 | 157 | 158 | while (!interrupt_received) { 159 | sleep(1); // Time doesn't really matter. 160 | } 161 | printf("\n");fflush(stdout); 162 | 163 | return 0; 164 | } --------------------------------------------------------------------------------