diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..745e9911 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environment info (please complete the following information):** + - Home Assistant or Openhab user + - Browser [e.g. chrome, safari] + - NSPanelManager version [Web UI lower left corner] + - NSPanel Firmware version [Visit NSPanel webpage to check] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore index ef166381..389cf192 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ docker/web/nspanelmanager/db.sqlite3 docker/web/nspanelmanager/secret.key docker/web/node_modules/ docker/nginx/sites-enabled/nspanelmanager.conf +docker/MQTTManager/fix_compile_commands_path.sh firmware/NSPanelManagerFirmware/data/config.json firmware/NSPanelManagerFirmware/.pio firmware/NSPanelManagerFirmware/littlefs.md5 diff --git a/NSPanelManager/web/nspanelmanager/manual.pdf b/NSPanelManager/web/nspanelmanager/manual.pdf deleted file mode 100644 index d51fc4bc..00000000 Binary files a/NSPanelManager/web/nspanelmanager/manual.pdf and /dev/null differ diff --git a/README.md b/README.md index 1b1b082e..f732da22 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,35 @@ +[![GitHub Release](https://img.shields.io/github/v/release/NSPManager/NSPanelManager?sort=semver)](https://github.com/NSPManager/NSPanelManager/releases) +[![Discord](https://img.shields.io/discord/1128437478261530675?logo=Discord&logoColor=%235865F2&label=Discord)](https://discord.gg/RwXvAH56fE) + # NSPanelManager -## What is it? +Standardised Smart Home control for Home Assistant and Openhab users using the original Sonoff NSPanel. -This project aims to solve the issue with having multiple Sonoff NSPanels installed around the house and having to make code changes as soon as you want to make a change. The project also aims to be simple to use, work for both Home Assistant and OpenHAB and any other home automation tool that can leverage MQTT. +NSPanel Manager is a custom software solution for the Sonoff NSPanel (not the NSPanel pro). +The software is designed to be easy to use on a day-to-day basis and to easily manage multiple NSPanels around +your home. -## The concept +The interface on the NSPanel itself has been designed to be intuitive to use for people of all ages and backgrounds. -The idea is that by using the same layout on all the different panels all users will quickly become accustomed to the layout and usage of the panel. +All the NSPanels that are installed with the NSPanel Manager solution communicate back to a Docker container that is +used to manage the panels, NSPanel Manager specific solutions and also all communication back and forth to/from +Home Assistant and/or OpenHAB. -# More information +# Complete walkthrough and installation guide on Youtube: + +[![Demonstration video](https://img.youtube.com/vi/EzDDtguwFd4/0.jpg)](https://www.youtube.com/watch?v=EzDDtguwFd4) + +# Slides of custom GUI for NSPanel -All information about this project on how it works and how to use it is available in the User & Technical reference manual available [nspanelmanager.com](https://nspanelmanager.com). +https://github.com/user-attachments/assets/a0bf2858-8384-4e67-b25e-44e3d41ccfa6 + +# Manager Web UI click-through + +https://github.com/user-attachments/assets/18ea4fe1-7b48-4b8e-a950-67c87f76b6e9 + +# More information -**Note:** For the best experience, download the PDF. It has helpful links (any text in blue) that doesn't work in the GitHub PDF viewer. Also, the GitHub PDF viewer only shows a few pages at the time. +All information about this project on how it works and how to use it is available at https://nspanelmanager.com # Further questions or discussion? diff --git a/convert_to_565_color_space.txt b/convert_to_565_color_space.txt new file mode 100644 index 00000000..7ba0a18a --- /dev/null +++ b/convert_to_565_color_space.txt @@ -0,0 +1,52 @@ + + +To convert a hexadecimal color code to RGB565 format, you generally need to extract the red, green, and blue components from the hex code and then map them to the 5-bit (red), 6-bit (green), and 5-bit (blue) ranges used in RGB565. + +Here's a breakdown of the process: + + Understand RGB565: RGB565 is a 16-bit color format where: + Red uses 5 bits. + Green uses 6 bits. + Blue uses 5 bits. + + Hexadecimal to RGB Components: A standard hex color code (e.g., #RRGGBB) represents 24 bits, with 8 bits for each of red, green, and blue. You'll need to parse these 8-bit values. + + Mapping to RGB565: + Red: The 8-bit red value (0-255) needs to be scaled down to 5 bits (0-31). This is typically done by right-shifting the 8-bit value by 3 bits (255 / 31 ≈ 8.2, so red >> 3). + Green: The 8-bit green value (0-255) needs to be scaled down to 6 bits (0-63). This is done by right-shifting the 8-bit value by 2 bits (255 / 63 ≈ 4.05, so green >> 2). + Blue: The 8-bit blue value (0-255) needs to be scaled down to 5 bits (0-31). Similar to red, this is done by right-shifting the 8-bit value by 3 bits (blue >> 3). + + Combining the Components: Once you have the scaled 5-bit red, 6-bit green, and 5-bit blue values, you combine them into a 16-bit integer. The typical arrangement is: + Red (5 bits) | Green (6 bits) | Blue (5 bits) + + This can be achieved using bitwise operations: + (red_5bit << 11) | (green_6bit << 5) | blue_5bit + +Example: + +Let's convert the hex color #FF0000 (red) to RGB565: + + Hex: #FF0000 + RGB888: Red = 255, Green = 0, Blue = 0 + Convert to RGB565: + Red: 255 >> 3 = 31 (which is 11111 in binary) + Green: 0 >> 2 = 0 (which is 000000 in binary) + Blue: 0 >> 3 = 0 (which is 00000 in binary) + Combine: + 31 << 11 = 1111100000000000 (binary) + 0 << 5 = 000000 (binary) + 0 = 00000 (binary) + Combined: 1111100000000000 (binary) = 0xF800 (hex) + 3 + +Online Converters: + +If you need to perform this conversion frequently, several online tools can help: + + Nebulacyd Hex to RGB565 Converter: https://nebulacyd.github.io/ + 2 + Rinky-Dink Electronics RGB565 Color Calculator: http://rinkydinkelectronics.com/calc_rgb565.php + 1 + +These tools allow you to input a hex color code and see its RGB565 representation. + diff --git a/design/font/MaterialDesignSmartHome_nspm_custom.sfd b/design/font/MaterialDesignSmartHome_nspm_custom.sfd index c2f4a885..157a4398 100644 --- a/design/font/MaterialDesignSmartHome_nspm_custom.sfd +++ b/design/font/MaterialDesignSmartHome_nspm_custom.sfd @@ -21,7 +21,7 @@ OS2Version: 4 OS2_WeightWidthSlopeOnly: 0 OS2_UseTypoMetrics: 1 CreationTime: -2082844800 -ModificationTime: 1758738393 +ModificationTime: 1767603227 PfmFamily: 17 TTFWeight: 400 TTFWidth: 5 @@ -89,7 +89,7 @@ DisplaySize: -48 AntiAlias: 1 FitToEm: 0 WinInfo: 0 27 9 -BeginChars: 65538 95 +BeginChars: 65538 96 StartChar: .notdef Encoding: 65536 -1 0 @@ -3672,80 +3672,8 @@ SplineSet EndSplineSet EndChar -StartChar: quotedbl -Encoding: 34 34 70 -Width: 512 -Flags: W -LayerCount: 2 -Fore -SplineSet -444 150 m 1,0,-1 - 394 137 l 1,1,-1 - 351 161 l 1,2,-1 - 351 223 l 1,3,-1 - 394 247 l 1,4,-1 - 444 234 l 1,5,-1 - 455 275 l 1,6,-1 - 417 285 l 1,7,-1 - 427 323 l 1,8,-1 - 385 334 l 1,9,-1 - 372 284 l 1,10,-1 - 330 260 l 1,11,-1 - 277 291 l 1,12,-1 - 277 339 l 1,13,-1 - 314 375 l 1,14,-1 - 284 405 l 1,15,-1 - 256 378 l 1,16,-1 - 228 405 l 1,17,-1 - 198 375 l 1,18,-1 - 235 339 l 1,19,-1 - 235 291 l 1,20,-1 - 181 260 l 1,21,-1 - 139 284 l 1,22,-1 - 126 334 l 1,23,-1 - 85 323 l 1,24,-1 - 95 285 l 1,25,-1 - 58 275 l 1,26,-1 - 69 234 l 1,27,-1 - 118 247 l 1,28,-1 - 161 223 l 1,29,-1 - 161 161 l 1,30,-1 - 118 137 l 1,31,-1 - 69 150 l 1,32,-1 - 58 109 l 1,33,-1 - 95 99 l 1,34,-1 - 85 61 l 1,35,-1 - 127 50 l 1,36,-1 - 140 100 l 1,37,-1 - 182 124 l 1,38,-1 - 235 93 l 1,39,-1 - 235 45 l 1,40,-1 - 198 9 l 1,41,-1 - 228 -21 l 1,42,-1 - 256 6 l 1,43,-1 - 284 -21 l 1,44,-1 - 314 9 l 1,45,-1 - 277 45 l 1,46,-1 - 277 93 l 1,47,-1 - 331 124 l 1,48,-1 - 373 100 l 1,49,-1 - 387 51 l 1,50,-1 - 427 61 l 1,51,-1 - 417 99 l 1,52,-1 - 454 109 l 1,53,-1 - 444 150 l 1,0,-1 -203 223 m 1,54,-1 - 256 254 l 1,55,-1 - 309 223 l 1,56,-1 - 309 161 l 1,57,-1 - 256 130 l 1,58,-1 - 203 161 l 1,59,-1 - 203 223 l 1,54,-1 -EndSplineSet -EndChar - StartChar: numbersign -Encoding: 35 35 71 +Encoding: 35 35 70 Width: 512 Flags: W LayerCount: 2 @@ -3798,7 +3726,7 @@ EndSplineSet EndChar StartChar: dollar -Encoding: 36 36 72 +Encoding: 36 36 71 Width: 512 Flags: W LayerCount: 2 @@ -3858,7 +3786,7 @@ EndSplineSet EndChar StartChar: percent -Encoding: 37 37 73 +Encoding: 37 37 72 Width: 512 Flags: W LayerCount: 2 @@ -3897,7 +3825,7 @@ EndSplineSet EndChar StartChar: ampersand -Encoding: 38 38 74 +Encoding: 38 38 73 Width: 512 Flags: W LayerCount: 2 @@ -3934,7 +3862,7 @@ EndSplineSet EndChar StartChar: quotesingle -Encoding: 39 39 75 +Encoding: 39 39 74 Width: 512 Flags: W LayerCount: 2 @@ -3965,7 +3893,7 @@ EndSplineSet EndChar StartChar: parenleft -Encoding: 40 40 76 +Encoding: 40 40 75 Width: 512 Flags: W LayerCount: 2 @@ -3987,7 +3915,7 @@ EndSplineSet EndChar StartChar: uni0009 -Encoding: 9 9 77 +Encoding: 9 9 76 Width: 512 LayerCount: 2 Back @@ -4590,7 +4518,7 @@ Validated: 1 EndChar StartChar: uni0011 -Encoding: 17 17 78 +Encoding: 17 17 77 Width: 512 LayerCount: 2 Back @@ -4896,7 +4824,7 @@ Validated: 1 EndChar StartChar: parenright -Encoding: 41 41 79 +Encoding: 41 41 78 Width: 512 Flags: W LayerCount: 2 @@ -4921,7 +4849,7 @@ EndSplineSet EndChar StartChar: asterisk -Encoding: 42 42 80 +Encoding: 42 42 79 Width: 512 Flags: W LayerCount: 2 @@ -4947,7 +4875,7 @@ EndSplineSet EndChar StartChar: plus -Encoding: 43 43 81 +Encoding: 43 43 80 Width: 512 Flags: W LayerCount: 2 @@ -5017,7 +4945,7 @@ EndSplineSet EndChar StartChar: hyphen -Encoding: 45 45 82 +Encoding: 45 45 81 Width: 512 Flags: W LayerCount: 2 @@ -5094,7 +5022,7 @@ EndSplineSet EndChar StartChar: comma -Encoding: 44 44 83 +Encoding: 44 44 82 Width: 512 Flags: W LayerCount: 2 @@ -5158,7 +5086,7 @@ EndSplineSet EndChar StartChar: period -Encoding: 46 46 84 +Encoding: 46 46 83 Width: 512 Flags: W LayerCount: 2 @@ -5201,7 +5129,7 @@ EndSplineSet EndChar StartChar: uni000D -Encoding: 13 13 85 +Encoding: 13 13 84 Width: 512 Flags: W LayerCount: 2 @@ -5210,7 +5138,7 @@ Validated: 1 EndChar StartChar: uni000E -Encoding: 14 14 86 +Encoding: 14 14 85 Width: 512 Flags: W LayerCount: 2 @@ -5219,7 +5147,7 @@ Validated: 1 EndChar StartChar: slash -Encoding: 47 47 87 +Encoding: 47 47 86 Width: 512 Flags: W LayerCount: 2 @@ -5244,7 +5172,7 @@ EndSplineSet EndChar StartChar: zero -Encoding: 48 48 88 +Encoding: 48 48 87 Width: 512 Flags: W LayerCount: 2 @@ -5269,7 +5197,7 @@ EndSplineSet EndChar StartChar: one -Encoding: 49 49 89 +Encoding: 49 49 88 Width: 512 Flags: W LayerCount: 2 @@ -5316,7 +5244,7 @@ EndSplineSet EndChar StartChar: two -Encoding: 50 50 90 +Encoding: 50 50 89 Width: 512 Flags: W LayerCount: 2 @@ -5390,7 +5318,7 @@ EndSplineSet EndChar StartChar: three -Encoding: 51 51 91 +Encoding: 51 51 90 Width: 512 Flags: W LayerCount: 2 @@ -5446,7 +5374,7 @@ EndSplineSet EndChar StartChar: four -Encoding: 52 52 92 +Encoding: 52 52 91 Width: 512 Flags: W LayerCount: 2 @@ -5509,7 +5437,7 @@ EndSplineSet EndChar StartChar: five -Encoding: 53 53 93 +Encoding: 53 53 92 Width: 512 Flags: W LayerCount: 2 @@ -5584,7 +5512,7 @@ EndSplineSet EndChar StartChar: six -Encoding: 54 54 94 +Encoding: 54 54 93 Width: 512 Flags: W LayerCount: 2 @@ -5660,5 +5588,124 @@ SplineSet 448 -18 448 -18 448 0 c 2,68,-1 EndSplineSet EndChar + +StartChar: seven +Encoding: 55 55 94 +Width: 512 +Flags: W +LayerCount: 2 +Fore +SplineSet +362 86 m 1,0,-1 + 316 132 l 1,1,2 + 341 157 341 157 341 192 c 0,3,4 + 341 215 341 215 329 235 c 1,5,-1 + 375 281 l 1,6,7 + 405 242 405 242 405 192 c 0,8,9 + 405 162 405 162 393.5 134.5 c 128,-1,10 + 382 107 382 107 362 86 c 1,0,-1 +256 341 m 0,11,12 + 306 341 306 341 345 311 c 1,13,-1 + 299 265 l 1,14,15 + 279 277 279 277 256 277 c 0,16,17 + 221 277 221 277 196 252 c 128,-1,18 + 171 227 171 227 171 192 c 128,-1,19 + 171 157 171 157 196 132 c 1,20,-1 + 150 86 l 1,21,22 + 130 107 130 107 118.5 134.5 c 128,-1,23 + 107 162 107 162 107 192 c 0,24,25 + 107 233 107 233 127 267 c 128,-1,26 + 147 301 147 301 181 321 c 128,-1,27 + 215 341 215 341 256 341 c 0,11,12 +256 405 m 0,28,29 + 214 405 214 405 174.5 389 c 128,-1,30 + 135 373 135 373 105 343 c 128,-1,31 + 75 313 75 313 59 273.5 c 128,-1,32 + 43 234 43 234 43 192 c 128,-1,33 + 43 150 43 150 59 110.5 c 128,-1,34 + 75 71 75 71 105 41 c 128,-1,35 + 135 11 135 11 174.5 -5 c 128,-1,36 + 214 -21 214 -21 256 -21 c 128,-1,37 + 298 -21 298 -21 337.5 -5 c 128,-1,38 + 377 11 377 11 407 41 c 128,-1,39 + 437 71 437 71 453 110.5 c 128,-1,40 + 469 150 469 150 469 192 c 0,41,42 + 469 250 469 250 440.5 299 c 128,-1,43 + 412 348 412 348 363 376.5 c 128,-1,44 + 314 405 314 405 256 405 c 0,28,29 +EndSplineSet +EndChar + +StartChar: eight +Encoding: 56 56 95 +Width: 512 +Flags: W +LayerCount: 2 +Fore +SplineSet +444 150 m 1,0,-1 + 394 137 l 1,1,-1 + 351 161 l 1,2,-1 + 351 223 l 1,3,-1 + 394 247 l 1,4,-1 + 444 234 l 1,5,-1 + 455 275 l 1,6,-1 + 417 285 l 1,7,-1 + 427 323 l 1,8,-1 + 385 334 l 1,9,-1 + 372 284 l 1,10,-1 + 330 260 l 1,11,-1 + 277 291 l 1,12,-1 + 277 339 l 1,13,-1 + 314 375 l 1,14,-1 + 284 405 l 1,15,-1 + 256 378 l 1,16,-1 + 228 405 l 1,17,-1 + 198 375 l 1,18,-1 + 235 339 l 1,19,-1 + 235 291 l 1,20,-1 + 181 260 l 1,21,-1 + 139 284 l 1,22,-1 + 126 334 l 1,23,-1 + 85 323 l 1,24,-1 + 95 285 l 1,25,-1 + 58 275 l 1,26,-1 + 69 234 l 1,27,-1 + 118 247 l 1,28,-1 + 161 223 l 1,29,-1 + 161 161 l 1,30,-1 + 118 137 l 1,31,-1 + 69 150 l 1,32,-1 + 58 109 l 1,33,-1 + 95 99 l 1,34,-1 + 85 61 l 1,35,-1 + 127 50 l 1,36,-1 + 140 100 l 1,37,-1 + 182 124 l 1,38,-1 + 235 93 l 1,39,-1 + 235 45 l 1,40,-1 + 198 9 l 1,41,-1 + 228 -21 l 1,42,-1 + 256 6 l 1,43,-1 + 284 -21 l 1,44,-1 + 314 9 l 1,45,-1 + 277 45 l 1,46,-1 + 277 93 l 1,47,-1 + 331 124 l 1,48,-1 + 373 100 l 1,49,-1 + 387 51 l 1,50,-1 + 427 61 l 1,51,-1 + 417 99 l 1,52,-1 + 454 109 l 1,53,-1 + 444 150 l 1,0,-1 +203 223 m 1,54,-1 + 256 254 l 1,55,-1 + 309 223 l 1,56,-1 + 309 161 l 1,57,-1 + 256 130 l 1,58,-1 + 203 161 l 1,59,-1 + 203 223 l 1,54,-1 +EndSplineSet +EndChar EndChars EndSplineFont diff --git a/docker-beta/config.yaml b/docker-beta/config.yaml index 42dc9caa..b10ab0a7 100644 --- a/docker-beta/config.yaml +++ b/docker-beta/config.yaml @@ -1,6 +1,6 @@ name: "NSPanel Manager BETA" description: "Container for Sonoff NSPanel management with a simple and intuitive UI" -version: 2.1.0-beta +version: 2.1.1-beta-h1 image: docker.io/nspanelmanager/nspanelmanager-beta-{arch} slug: "nspanelmanager-beta" url: "https://github.com/NSPManager/NSPanelManager" diff --git a/docker/Dockerfile b/docker/Dockerfile index c7b48076..57cf5d21 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,7 +11,7 @@ COPY MQTTManager/ /MQTTManager/ # Only build MQTTManager during Docker build if is is not a devel mode. RUN if [ "$IS_DEVEL" != "yes" ]; then apt-get update \ - && apt-get -y install cmake build-essential curl \ + && apt-get -y install cmake build-essential curl libmagick++-dev \ && pip install -U conan; fi RUN if [ "$IS_DEVEL" != "yes" ]; then conan profile detect --force && echo 'core.cache:storage_path=/MQTTManager/conan_cache/' > ~/.conan2/global.conf \ @@ -45,6 +45,9 @@ RUN if [ "$IS_DEVEL" == "yes" ]; then apt-get install -y --no-install-recommends #&& rm -rf /var/lib/apt/lists/* ; fi +# Install ImageMagick to be able to process images and send them to the NSPanel +RUN apt-get install -y libmagick++-dev + RUN apt-get install -y nginx RUN pip install --no-cache-dir -r requirements.txt diff --git a/docker/HMI_files/tft_automation/eu/gui.HMI b/docker/HMI_files/tft_automation/eu/gui.HMI index fce5df77..ae04578d 100644 Binary files a/docker/HMI_files/tft_automation/eu/gui.HMI and b/docker/HMI_files/tft_automation/eu/gui.HMI differ diff --git a/docker/HMI_files/tft_automation/eu/output_tft1/gui.tft b/docker/HMI_files/tft_automation/eu/output_tft1/gui.tft index 9b6cab4d..b0a38bf0 100644 Binary files a/docker/HMI_files/tft_automation/eu/output_tft1/gui.tft and b/docker/HMI_files/tft_automation/eu/output_tft1/gui.tft differ diff --git a/docker/HMI_files/tft_automation/eu/output_tft2/gui.tft b/docker/HMI_files/tft_automation/eu/output_tft2/gui.tft index 4b8d2529..36dfd68b 100644 Binary files a/docker/HMI_files/tft_automation/eu/output_tft2/gui.tft and b/docker/HMI_files/tft_automation/eu/output_tft2/gui.tft differ diff --git a/docker/HMI_files/tft_automation/eu/output_tft3/gui.tft b/docker/HMI_files/tft_automation/eu/output_tft3/gui.tft index 93783e11..0064f467 100644 Binary files a/docker/HMI_files/tft_automation/eu/output_tft3/gui.tft and b/docker/HMI_files/tft_automation/eu/output_tft3/gui.tft differ diff --git a/docker/HMI_files/tft_automation/eu/output_tft4/gui.tft b/docker/HMI_files/tft_automation/eu/output_tft4/gui.tft index 64899987..f887b606 100644 Binary files a/docker/HMI_files/tft_automation/eu/output_tft4/gui.tft and b/docker/HMI_files/tft_automation/eu/output_tft4/gui.tft differ diff --git a/docker/HMI_files/tft_automation/eu/pictures_tft1/180_thermostat_background.bmp b/docker/HMI_files/tft_automation/eu/pictures_tft1/180_thermostat_background.bmp new file mode 100644 index 00000000..0a96b133 Binary files /dev/null and b/docker/HMI_files/tft_automation/eu/pictures_tft1/180_thermostat_background.bmp differ diff --git a/docker/HMI_files/tft_automation/eu/pictures_tft1/all_numbered/180.bmp b/docker/HMI_files/tft_automation/eu/pictures_tft1/all_numbered/180.bmp new file mode 100644 index 00000000..8e807101 Binary files /dev/null and b/docker/HMI_files/tft_automation/eu/pictures_tft1/all_numbered/180.bmp differ diff --git a/docker/HMI_files/tft_automation/eu/pictures_tft2/180_thermostat_background.bmp b/docker/HMI_files/tft_automation/eu/pictures_tft2/180_thermostat_background.bmp new file mode 100644 index 00000000..f22183c2 Binary files /dev/null and b/docker/HMI_files/tft_automation/eu/pictures_tft2/180_thermostat_background.bmp differ diff --git a/docker/HMI_files/tft_automation/eu/pictures_tft2/all_numbered/180.bmp b/docker/HMI_files/tft_automation/eu/pictures_tft2/all_numbered/180.bmp new file mode 100644 index 00000000..59ba1236 Binary files /dev/null and b/docker/HMI_files/tft_automation/eu/pictures_tft2/all_numbered/180.bmp differ diff --git a/docker/HMI_files/tft_automation/eu/pictures_tft3/180_thermostat_background.bmp b/docker/HMI_files/tft_automation/eu/pictures_tft3/180_thermostat_background.bmp new file mode 100644 index 00000000..225e9461 Binary files /dev/null and b/docker/HMI_files/tft_automation/eu/pictures_tft3/180_thermostat_background.bmp differ diff --git a/docker/HMI_files/tft_automation/eu/pictures_tft3/all_numbered/180.bmp b/docker/HMI_files/tft_automation/eu/pictures_tft3/all_numbered/180.bmp new file mode 100644 index 00000000..ee170f26 Binary files /dev/null and b/docker/HMI_files/tft_automation/eu/pictures_tft3/all_numbered/180.bmp differ diff --git a/docker/HMI_files/tft_automation/eu/pictures_tft4/180_thermostat_background.bmp b/docker/HMI_files/tft_automation/eu/pictures_tft4/180_thermostat_background.bmp new file mode 100644 index 00000000..762cd40c Binary files /dev/null and b/docker/HMI_files/tft_automation/eu/pictures_tft4/180_thermostat_background.bmp differ diff --git a/docker/HMI_files/tft_automation/eu/pictures_tft4/all_numbered/180.bmp b/docker/HMI_files/tft_automation/eu/pictures_tft4/all_numbered/180.bmp new file mode 100644 index 00000000..f88f4fae Binary files /dev/null and b/docker/HMI_files/tft_automation/eu/pictures_tft4/all_numbered/180.bmp differ diff --git a/docker/HMI_files/tft_automation/eu/tft_EU.xcf b/docker/HMI_files/tft_automation/eu/tft_EU.xcf index 456828fc..23aaa201 100644 Binary files a/docker/HMI_files/tft_automation/eu/tft_EU.xcf and b/docker/HMI_files/tft_automation/eu/tft_EU.xcf differ diff --git a/docker/HMI_files/tft_automation/eu/tftautomation.ahk b/docker/HMI_files/tft_automation/eu/tftautomation.ahk index 7659eec2..8bdc4fe5 100644 --- a/docker/HMI_files/tft_automation/eu/tftautomation.ahk +++ b/docker/HMI_files/tft_automation/eu/tftautomation.ahk @@ -16,7 +16,7 @@ outputTftDir4 := "C:\Github\NSPanelManager\docker\HMI_files\tft_automation\eu\o ; ── STEP 1: Open HMI file (starts editor too) ─ Run(hmiFile) WinWaitActive("Nextion Editor") ; Wait for the window title to show up -Sleep(2000) ; extra buffer to let it finish loading fully +Sleep(5000) ; extra buffer to let it finish loading fully ; ── STEP 2: Delete all images ──────────────── Click(195, 444) ; Trash icon @@ -49,16 +49,18 @@ Sleep(5000) ;WinWaitActive("Nextion Editor", "Import successful", 5) ; title + text filter ;Sleep(100) ; give focus to OK Send("{Enter}") ; press OK -Sleep(1000) +Sleep(2000) ; ── STEP 4: Build TFT Output ───────────────── ; Activate the editor window just in case -WinActivate("Nextion Editor") -Sleep(100) +;WinActivate("Nextion Editor") +;Sleep(100) ;Click the File menu — get coordinates using Window Spy Click(23, 46) ; adjust this to your File menu position -Sleep(200) ; let the menu open +Sleep(100) +Click(23, 46) ; adjust this to your File menu position +Sleep(2000) ; let the menu open ;Navigate to “TFT Output” using arrow keys Send("{Down 1}") ; move to first item — adjust if needed @@ -105,21 +107,23 @@ Sleep(100) Send("{Enter}") ; Import into Nextion WinWaitClose("ahk_class #32770", , 5) -Sleep(3000) +Sleep(5000) ; wait for the success pop-up and dismiss it ;WinWaitActive("Nextion Editor", "Import successful", 5) ; title + text filter ;Sleep(100) ; give focus to OK Send("{Enter}") ; press OK -Sleep(1000) +Sleep(2000) ; ── STEP 4: Build TFT Output ───────────────── ; Activate the editor window just in case -WinActivate("Nextion Editor") -Sleep(100) +;WinActivate("Nextion Editor") +;Sleep(100) ;Click the File menu — get coordinates using Window Spy Click(23, 46) ; adjust this to your File menu position -Sleep(200) ; let the menu open +Sleep(100) +Click(23, 46) ; adjust this to your File menu position +Sleep(2000) ; let the menu open ;Navigate to “TFT Output” using arrow keys Send("{Down 1}") ; move to first item — adjust if needed @@ -164,21 +168,23 @@ Sleep(100) Send("{Enter}") ; Import into Nextion WinWaitClose("ahk_class #32770", , 5) -Sleep(3000) +Sleep(5000) ; wait for the success pop-up and dismiss it ;WinWaitActive("Nextion Editor", "Import successful", 5) ; title + text filter ;Sleep(100) ; give focus to OK Send("{Enter}") ; press OK -Sleep(1000) +Sleep(2000) ; ── STEP 4: Build TFT Output ───────────────── ; Activate the editor window just in case -WinActivate("Nextion Editor") -Sleep(100) +;WinActivate("Nextion Editor") +;Sleep(100) ;Click the File menu — get coordinates using Window Spy Click(23, 46) ; adjust this to your File menu position -Sleep(200) ; let the menu open +Sleep(100) +Click(23, 46) ; adjust this to your File menu position +Sleep(2000) ; let the menu open ;Navigate to “TFT Output” using arrow keys Send("{Down 1}") ; move to first item — adjust if needed @@ -223,21 +229,23 @@ Sleep(100) Send("{Enter}") ; Import into Nextion WinWaitClose("ahk_class #32770", , 5) -Sleep(3000) +Sleep(5000) ; wait for the success pop-up and dismiss it ;WinWaitActive("Nextion Editor", "Import successful", 5) ; title + text filter ;Sleep(100) ; give focus to OK Send("{Enter}") ; press OK -Sleep(1000) +Sleep(2000) ; ── STEP 4: Build TFT Output ───────────────── ; Activate the editor window just in case -WinActivate("Nextion Editor") -Sleep(100) +;WinActivate("Nextion Editor") +;Sleep(100) ;Click the File menu — get coordinates using Window Spy Click(23, 46) ; adjust this to your File menu position -Sleep(200) ; let the menu open +Sleep(100) +Click(23, 46) ; adjust this to your File menu position +Sleep(2000) ; let the menu open ;Navigate to “TFT Output” using arrow keys Send("{Down 1}") ; move to first item — adjust if needed diff --git a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft1.scm b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft1.scm index 4eef37bb..947a5645 100644 --- a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft1.scm +++ b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft1.scm @@ -43,7 +43,6 @@ (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_TV")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ALARM PAGE")) 0) - (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "SCREENCLEAN_PAGE")) 0) (let* ((newImage (car (gimp-image-duplicate image)))) @@ -561,6 +560,26 @@ ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "BACKGROUND")) 0) + +;; THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE + + ;MAKE BACKGROUND PAGE VISIBLE + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 1) + + (let* + ((newImage (car (gimp-image-duplicate image)))) + (gimp-image-merge-visible-layers newImage 1) + (let* + ((activeLayer (car (gimp-image-get-active-layer newImage)))) + (gimp-file-save RUN-NONINTERACTIVE image activeLayer "pictures_tft1/180_thermostat_background.bmp" "pictures_tft1/180_thermostat_background.bmp") + ) + (gimp-image-delete newImage) + ) + + + ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) +;;--------------------------------------------------------------------------------------------------------- (gimp-image-delete image) ) ) diff --git a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft2.scm b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft2.scm index 59551e48..ac36724e 100644 --- a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft2.scm +++ b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft2.scm @@ -39,7 +39,6 @@ (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_TV")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ALARM PAGE")) 0) - (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "SCREENCLEAN_PAGE")) 0) (let* ((newImage (car (gimp-image-duplicate image)))) @@ -557,7 +556,26 @@ ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "BACKGROUND")) 0) +;; THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE + + ;MAKE BACKGROUND PAGE VISIBLE + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 1) + + (let* + ((newImage (car (gimp-image-duplicate image)))) + (gimp-image-merge-visible-layers newImage 1) + (let* + ((activeLayer (car (gimp-image-get-active-layer newImage)))) + (gimp-file-save RUN-NONINTERACTIVE image activeLayer "pictures_tft2/180_thermostat_background.bmp" "pictures_tft2/180_thermostat_background.bmp") + ) + (gimp-image-delete newImage) + ) + + + ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) +;;--------------------------------------------------------------------------------------------------------- (gimp-image-delete image) ) ) diff --git a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft3.scm b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft3.scm index 022d8a84..a4d1a7e1 100644 --- a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft3.scm +++ b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft3.scm @@ -39,7 +39,6 @@ (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_TV")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ALARM PAGE")) 0) - (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "SCREENCLEAN_PAGE")) 0) (let* ((newImage (car (gimp-image-duplicate image)))) @@ -557,7 +556,26 @@ ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "BACKGROUND")) 0) +;; THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE + + ;MAKE BACKGROUND PAGE VISIBLE + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 1) + + (let* + ((newImage (car (gimp-image-duplicate image)))) + (gimp-image-merge-visible-layers newImage 1) + (let* + ((activeLayer (car (gimp-image-get-active-layer newImage)))) + (gimp-file-save RUN-NONINTERACTIVE image activeLayer "pictures_tft3/180_thermostat_background.bmp" "pictures_tft3/180_thermostat_background.bmp") + ) + (gimp-image-delete newImage) + ) + + + ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) +;;--------------------------------------------------------------------------------------------------------- (gimp-image-delete image) ) ) diff --git a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft4.scm b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft4.scm index 9ce79670..1050be69 100644 --- a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft4.scm +++ b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-eu-tft4.scm @@ -39,7 +39,6 @@ (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_TV")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ALARM PAGE")) 0) - (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "SCREENCLEAN_PAGE")) 0) (let* ((newImage (car (gimp-image-duplicate image)))) @@ -557,7 +556,26 @@ ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "BACKGROUND")) 0) +;; THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE + + ;MAKE BACKGROUND PAGE VISIBLE + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 1) + + (let* + ((newImage (car (gimp-image-duplicate image)))) + (gimp-image-merge-visible-layers newImage 1) + (let* + ((activeLayer (car (gimp-image-get-active-layer newImage)))) + (gimp-file-save RUN-NONINTERACTIVE image activeLayer "pictures_tft4/180_thermostat_background.bmp" "pictures_tft4/180_thermostat_background.bmp") + ) + (gimp-image-delete newImage) + ) + + + ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) +;;--------------------------------------------------------------------------------------------------------- (gimp-image-delete image) ) ) diff --git a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft1.scm b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft1.scm index 2ec9f352..1d756804 100644 --- a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft1.scm +++ b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft1.scm @@ -35,11 +35,10 @@ (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "SCREENSAVER_PAGE")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "DROPDOWN_PAGE")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "FIRST_PAGE_SOUND")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_BLINDS")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_TV")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ALARM PAGE")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "SCREENCLEAN_PAGE")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_BLINDS")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_TV")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ALARM_PAGE")) 0) (let* ((newImage (car (gimp-image-duplicate image)))) @@ -556,7 +555,27 @@ ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "BACKGROUND")) 0) + +;;------------------------------------------------------------------------------------------------------------------------ +;; THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE + ;MAKE THERMOSTAT PAGE VISIBLE + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 1) + + (let* + ((newImage (car (gimp-image-duplicate image)))) + (gimp-image-merge-visible-layers newImage 1) + (let* + ((activeLayer (car (gimp-image-get-active-layer newImage)))) + (gimp-file-save RUN-NONINTERACTIVE image activeLayer "pictures_tft1/180_thermostat_background.bmp" "pictures_tft1/180_thermostat_background.bmp") + ) + (gimp-image-delete newImage) + ) + + + ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) +;;--------------------------------------------------------------------------------------------------------- (gimp-image-delete image) ) ) diff --git a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft2.scm b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft2.scm index 42dcf4d9..4c2586ee 100644 --- a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft2.scm +++ b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft2.scm @@ -35,11 +35,10 @@ (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "SCREENSAVER_PAGE")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "DROPDOWN_PAGE")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "FIRST_PAGE_SOUND")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_BLINDS")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_TV")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ALARM PAGE")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "SCREENCLEAN_PAGE")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_BLINDS")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_TV")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ALARM_PAGE")) 0) (let* ((newImage (car (gimp-image-duplicate image)))) @@ -556,7 +555,27 @@ ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "BACKGROUND")) 0) + +;;------------------------------------------------------------------------------------------------------------------------ +;; THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE + ;MAKE THERMOSTAT PAGE VISIBLE + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 1) + + (let* + ((newImage (car (gimp-image-duplicate image)))) + (gimp-image-merge-visible-layers newImage 1) + (let* + ((activeLayer (car (gimp-image-get-active-layer newImage)))) + (gimp-file-save RUN-NONINTERACTIVE image activeLayer "pictures_tft2/180_thermostat_background.bmp" "pictures_tft2/180_thermostat_background.bmp") + ) + (gimp-image-delete newImage) + ) + + + ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) +;;--------------------------------------------------------------------------------------------------------- (gimp-image-delete image) ) ) diff --git a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft3.scm b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft3.scm index c320d826..04b54ea2 100644 --- a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft3.scm +++ b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft3.scm @@ -35,11 +35,10 @@ (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "SCREENSAVER_PAGE")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "DROPDOWN_PAGE")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "FIRST_PAGE_SOUND")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_BLINDS")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_TV")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ALARM PAGE")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "SCREENCLEAN_PAGE")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_BLINDS")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_TV")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ALARM_PAGE")) 0) (let* ((newImage (car (gimp-image-duplicate image)))) @@ -556,7 +555,27 @@ ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "BACKGROUND")) 0) + +;;------------------------------------------------------------------------------------------------------------------------ +;; THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE + ;MAKE THERMOSTAT PAGE VISIBLE + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 1) + + (let* + ((newImage (car (gimp-image-duplicate image)))) + (gimp-image-merge-visible-layers newImage 1) + (let* + ((activeLayer (car (gimp-image-get-active-layer newImage)))) + (gimp-file-save RUN-NONINTERACTIVE image activeLayer "pictures_tft3/180_thermostat_background.bmp" "pictures_tft3/180_thermostat_background.bmp") + ) + (gimp-image-delete newImage) + ) + + + ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) +;;--------------------------------------------------------------------------------------------------------- (gimp-image-delete image) ) ) diff --git a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft4.scm b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft4.scm index 2083e2c8..b4c3f563 100644 --- a/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft4.scm +++ b/docker/HMI_files/tft_automation/gimp_scripts/nspanel-us-tft4.scm @@ -35,11 +35,10 @@ (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "SCREENSAVER_PAGE")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "DROPDOWN_PAGE")) 0) (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "FIRST_PAGE_SOUND")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_BLINDS")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_TV")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ALARM PAGE")) 0) - ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "SCREENCLEAN_PAGE")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_BLINDS")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_TV")) 0) + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ALARM_PAGE")) 0) (let* ((newImage (car (gimp-image-duplicate image)))) @@ -556,7 +555,27 @@ ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM ;(gimp-item-set-visible (car (gimp-image-get-layer-by-name image "BACKGROUND")) 0) + +;;------------------------------------------------------------------------------------------------------------------------ +;; THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE THERMOSTAT_PAGE + ;MAKE THERMOSTAT PAGE VISIBLE + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 1) + + (let* + ((newImage (car (gimp-image-duplicate image)))) + (gimp-image-merge-visible-layers newImage 1) + (let* + ((activeLayer (car (gimp-image-get-active-layer newImage)))) + (gimp-file-save RUN-NONINTERACTIVE image activeLayer "pictures_tft4/180_thermostat_background.bmp" "pictures_tft4/180_thermostat_background.bmp") + ) + (gimp-image-delete newImage) + ) + + + ;MAKE BACKGROUND PAGE INVISIBLE TO PREPARE FOR NEXT ROOM + (gimp-item-set-visible (car (gimp-image-get-layer-by-name image "ENTITY_THERMOSTAT")) 0) +;;--------------------------------------------------------------------------------------------------------- (gimp-image-delete image) ) ) diff --git a/docker/HMI_files/tft_automation/us/gui.HMI b/docker/HMI_files/tft_automation/us/gui.HMI index fd4b0cfb..97476851 100644 Binary files a/docker/HMI_files/tft_automation/us/gui.HMI and b/docker/HMI_files/tft_automation/us/gui.HMI differ diff --git a/docker/HMI_files/tft_automation/us/output_tft1/gui.tft b/docker/HMI_files/tft_automation/us/output_tft1/gui.tft index a2fc1ea1..11465489 100644 Binary files a/docker/HMI_files/tft_automation/us/output_tft1/gui.tft and b/docker/HMI_files/tft_automation/us/output_tft1/gui.tft differ diff --git a/docker/HMI_files/tft_automation/us/output_tft2/gui.tft b/docker/HMI_files/tft_automation/us/output_tft2/gui.tft index f6c66b66..79162f87 100644 Binary files a/docker/HMI_files/tft_automation/us/output_tft2/gui.tft and b/docker/HMI_files/tft_automation/us/output_tft2/gui.tft differ diff --git a/docker/HMI_files/tft_automation/us/output_tft3/gui.tft b/docker/HMI_files/tft_automation/us/output_tft3/gui.tft index c775aacc..70467d6c 100644 Binary files a/docker/HMI_files/tft_automation/us/output_tft3/gui.tft and b/docker/HMI_files/tft_automation/us/output_tft3/gui.tft differ diff --git a/docker/HMI_files/tft_automation/us/output_tft4/gui.tft b/docker/HMI_files/tft_automation/us/output_tft4/gui.tft index f6d8076e..f5a0f6a5 100644 Binary files a/docker/HMI_files/tft_automation/us/output_tft4/gui.tft and b/docker/HMI_files/tft_automation/us/output_tft4/gui.tft differ diff --git a/docker/HMI_files/tft_automation/us/pictures_tft1/180_thermostat_background.bmp b/docker/HMI_files/tft_automation/us/pictures_tft1/180_thermostat_background.bmp new file mode 100644 index 00000000..55b4d195 Binary files /dev/null and b/docker/HMI_files/tft_automation/us/pictures_tft1/180_thermostat_background.bmp differ diff --git a/docker/HMI_files/tft_automation/us/pictures_tft1/all_numbered/180.bmp b/docker/HMI_files/tft_automation/us/pictures_tft1/all_numbered/180.bmp new file mode 100644 index 00000000..7241c1d1 Binary files /dev/null and b/docker/HMI_files/tft_automation/us/pictures_tft1/all_numbered/180.bmp differ diff --git a/docker/HMI_files/tft_automation/us/pictures_tft2/180_thermostat_background.bmp b/docker/HMI_files/tft_automation/us/pictures_tft2/180_thermostat_background.bmp new file mode 100644 index 00000000..877a453e Binary files /dev/null and b/docker/HMI_files/tft_automation/us/pictures_tft2/180_thermostat_background.bmp differ diff --git a/docker/HMI_files/tft_automation/us/pictures_tft2/all_numbered/180.bmp b/docker/HMI_files/tft_automation/us/pictures_tft2/all_numbered/180.bmp new file mode 100644 index 00000000..a5c40ad7 Binary files /dev/null and b/docker/HMI_files/tft_automation/us/pictures_tft2/all_numbered/180.bmp differ diff --git a/docker/HMI_files/tft_automation/us/pictures_tft3/180_thermostat_background.bmp b/docker/HMI_files/tft_automation/us/pictures_tft3/180_thermostat_background.bmp new file mode 100644 index 00000000..af59dbd9 Binary files /dev/null and b/docker/HMI_files/tft_automation/us/pictures_tft3/180_thermostat_background.bmp differ diff --git a/docker/HMI_files/tft_automation/us/pictures_tft3/all_numbered/180.bmp b/docker/HMI_files/tft_automation/us/pictures_tft3/all_numbered/180.bmp new file mode 100644 index 00000000..094d0717 Binary files /dev/null and b/docker/HMI_files/tft_automation/us/pictures_tft3/all_numbered/180.bmp differ diff --git a/docker/HMI_files/tft_automation/us/pictures_tft4/180_thermostat_background.bmp b/docker/HMI_files/tft_automation/us/pictures_tft4/180_thermostat_background.bmp new file mode 100644 index 00000000..96bcd394 Binary files /dev/null and b/docker/HMI_files/tft_automation/us/pictures_tft4/180_thermostat_background.bmp differ diff --git a/docker/HMI_files/tft_automation/us/pictures_tft4/all_numbered/180.bmp b/docker/HMI_files/tft_automation/us/pictures_tft4/all_numbered/180.bmp new file mode 100644 index 00000000..2f0e2343 Binary files /dev/null and b/docker/HMI_files/tft_automation/us/pictures_tft4/all_numbered/180.bmp differ diff --git a/docker/HMI_files/tft_automation/us/tft_US.xcf b/docker/HMI_files/tft_automation/us/tft_US.xcf index 6a10dd2b..7427273d 100644 Binary files a/docker/HMI_files/tft_automation/us/tft_US.xcf and b/docker/HMI_files/tft_automation/us/tft_US.xcf differ diff --git a/docker/HMI_files/tft_automation/us/tftautomation.ahk b/docker/HMI_files/tft_automation/us/tftautomation.ahk index 91d7c057..5a5239ed 100644 --- a/docker/HMI_files/tft_automation/us/tftautomation.ahk +++ b/docker/HMI_files/tft_automation/us/tftautomation.ahk @@ -16,7 +16,7 @@ outputTftDir4 := "C:\Github\NSPanelManager\docker\HMI_files\tft_automation\us\o ; ── STEP 1: Open HMI file (starts editor too) ─ Run(hmiFile) WinWaitActive("Nextion Editor") ; Wait for the window title to show up -Sleep(2000) ; extra buffer to let it finish loading fully +Sleep(5000) ; extra buffer to let it finish loading fully ; ── STEP 2: Delete all images ──────────────── Click(195, 444) ; Trash icon @@ -49,7 +49,7 @@ Sleep(5000) ;WinWaitActive("Nextion Editor", "Import successful", 5) ; title + text filter ;Sleep(100) ; give focus to OK Send("{Enter}") ; press OK -Sleep(1000) +Sleep(2000) ; ── STEP 4: Build TFT Output ───────────────── ; Activate the editor window just in case @@ -58,7 +58,9 @@ Sleep(100) ;Click the File menu — get coordinates using Window Spy Click(23, 46) ; adjust this to your File menu position -Sleep(200) ; let the menu open +Sleep(100) +Click(23, 46) ; adjust this to your File menu position +Sleep(2000) ; let the menu open ;Navigate to “TFT Output” using arrow keys Send("{Down 1}") ; move to first item — adjust if needed @@ -105,12 +107,12 @@ Sleep(100) Send("{Enter}") ; Import into Nextion WinWaitClose("ahk_class #32770", , 5) -Sleep(3000) +Sleep(5000) ; wait for the success pop-up and dismiss it ;WinWaitActive("Nextion Editor", "Import successful", 5) ; title + text filter ;Sleep(100) ; give focus to OK Send("{Enter}") ; press OK -Sleep(1000) +Sleep(2000) ; ── STEP 4: Build TFT Output ───────────────── ; Activate the editor window just in case @@ -119,7 +121,9 @@ Sleep(100) ;Click the File menu — get coordinates using Window Spy Click(23, 46) ; adjust this to your File menu position -Sleep(200) ; let the menu open +Sleep(100) +Click(23, 46) ; adjust this to your File menu position +Sleep(2000) ; let the menu open ;Navigate to “TFT Output” using arrow keys Send("{Down 1}") ; move to first item — adjust if needed @@ -164,12 +168,12 @@ Sleep(100) Send("{Enter}") ; Import into Nextion WinWaitClose("ahk_class #32770", , 5) -Sleep(3000) +Sleep(5000) ; wait for the success pop-up and dismiss it ;WinWaitActive("Nextion Editor", "Import successful", 5) ; title + text filter ;Sleep(100) ; give focus to OK Send("{Enter}") ; press OK -Sleep(1000) +Sleep(2000) ; ── STEP 4: Build TFT Output ───────────────── ; Activate the editor window just in case @@ -178,7 +182,9 @@ Sleep(100) ;Click the File menu — get coordinates using Window Spy Click(23, 46) ; adjust this to your File menu position -Sleep(200) ; let the menu open +Sleep(100) +Click(23, 46) ; adjust this to your File menu position +Sleep(2000) ; let the menu open ;Navigate to “TFT Output” using arrow keys Send("{Down 1}") ; move to first item — adjust if needed @@ -223,12 +229,12 @@ Sleep(100) Send("{Enter}") ; Import into Nextion WinWaitClose("ahk_class #32770", , 5) -Sleep(3000) +Sleep(5000) ; wait for the success pop-up and dismiss it ;WinWaitActive("Nextion Editor", "Import successful", 5) ; title + text filter ;Sleep(100) ; give focus to OK Send("{Enter}") ; press OK -Sleep(1000) +Sleep(2000) ; ── STEP 4: Build TFT Output ───────────────── ; Activate the editor window just in case @@ -237,7 +243,9 @@ Sleep(100) ;Click the File menu — get coordinates using Window Spy Click(23, 46) ; adjust this to your File menu position -Sleep(200) ; let the menu open +Sleep(100) +Click(23, 46) ; adjust this to your File menu position +Sleep(2000) ; let the menu open ;Navigate to “TFT Output” using arrow keys Send("{Down 1}") ; move to first item — adjust if needed diff --git a/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft1/gui.tft b/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft1/gui.tft index 49350931..6bdcc3d8 100644 Binary files a/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft1/gui.tft and b/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft1/gui.tft differ diff --git a/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft2/gui.tft b/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft2/gui.tft index 3104b1a6..64f6500b 100644 Binary files a/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft2/gui.tft and b/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft2/gui.tft differ diff --git a/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft3/gui.tft b/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft3/gui.tft index d1aa2706..029a16fe 100644 Binary files a/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft3/gui.tft and b/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft3/gui.tft differ diff --git a/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft4/gui.tft b/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft4/gui.tft index 2f7ed37a..478e71a8 100644 Binary files a/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft4/gui.tft and b/docker/HMI_files/tft_automation/us_horizontal_mirrored/output_tft4/gui.tft differ diff --git a/docker/HMI_files/tft_automation/us_horizontal_mirrored/tftautomation.ahk b/docker/HMI_files/tft_automation/us_horizontal_mirrored/tftautomation.ahk index 007ea7a1..25eaf80f 100644 --- a/docker/HMI_files/tft_automation/us_horizontal_mirrored/tftautomation.ahk +++ b/docker/HMI_files/tft_automation/us_horizontal_mirrored/tftautomation.ahk @@ -16,7 +16,7 @@ outputTftDir4 := "C:\Github\NSPanelManager\docker\HMI_files\tft_automation\us_h ; ── STEP 1: Open HMI file (starts editor too) ─ Run(hmiFile) WinWaitActive("Nextion Editor") ; Wait for the window title to show up -Sleep(2000) ; extra buffer to let it finish loading fully +Sleep(5000) ; extra buffer to let it finish loading fully ; ── STEP 2: Delete all images ──────────────── Click(195, 444) ; Trash icon @@ -49,7 +49,7 @@ Sleep(5000) ;WinWaitActive("Nextion Editor", "Import successful", 5) ; title + text filter ;Sleep(100) ; give focus to OK Send("{Enter}") ; press OK -Sleep(1000) +Sleep(2000) ; ── STEP 4: Build TFT Output ───────────────── ; Activate the editor window just in case @@ -58,7 +58,9 @@ Sleep(100) ;Click the File menu — get coordinates using Window Spy Click(23, 46) ; adjust this to your File menu position -Sleep(200) ; let the menu open +Sleep(100) +Click(23, 46) ; adjust this to your File menu position +Sleep(2000) ; let the menu open ;Navigate to “TFT Output” using arrow keys Send("{Down 1}") ; move to first item — adjust if needed @@ -105,12 +107,12 @@ Sleep(100) Send("{Enter}") ; Import into Nextion WinWaitClose("ahk_class #32770", , 5) -Sleep(3000) +Sleep(5000) ; wait for the success pop-up and dismiss it ;WinWaitActive("Nextion Editor", "Import successful", 5) ; title + text filter ;Sleep(100) ; give focus to OK Send("{Enter}") ; press OK -Sleep(1000) +Sleep(2000) ; ── STEP 4: Build TFT Output ───────────────── ; Activate the editor window just in case @@ -119,7 +121,9 @@ Sleep(100) ;Click the File menu — get coordinates using Window Spy Click(23, 46) ; adjust this to your File menu position -Sleep(200) ; let the menu open +Sleep(100) +Click(23, 46) ; adjust this to your File menu position +Sleep(2000) ; let the menu open ;Navigate to “TFT Output” using arrow keys Send("{Down 1}") ; move to first item — adjust if needed @@ -164,12 +168,12 @@ Sleep(100) Send("{Enter}") ; Import into Nextion WinWaitClose("ahk_class #32770", , 5) -Sleep(3000) +Sleep(5000) ; wait for the success pop-up and dismiss it ;WinWaitActive("Nextion Editor", "Import successful", 5) ; title + text filter ;Sleep(100) ; give focus to OK Send("{Enter}") ; press OK -Sleep(1000) +Sleep(2000) ; ── STEP 4: Build TFT Output ───────────────── ; Activate the editor window just in case @@ -178,7 +182,9 @@ Sleep(100) ;Click the File menu — get coordinates using Window Spy Click(23, 46) ; adjust this to your File menu position -Sleep(200) ; let the menu open +Sleep(100) +Click(23, 46) ; adjust this to your File menu position +Sleep(2000) ; let the menu open ;Navigate to “TFT Output” using arrow keys Send("{Down 1}") ; move to first item — adjust if needed @@ -223,12 +229,12 @@ Sleep(100) Send("{Enter}") ; Import into Nextion WinWaitClose("ahk_class #32770", , 5) -Sleep(3000) +Sleep(5000) ; wait for the success pop-up and dismiss it ;WinWaitActive("Nextion Editor", "Import successful", 5) ; title + text filter ;Sleep(100) ; give focus to OK Send("{Enter}") ; press OK -Sleep(1000) +Sleep(2000) ; ── STEP 4: Build TFT Output ───────────────── ; Activate the editor window just in case @@ -237,7 +243,9 @@ Sleep(100) ;Click the File menu — get coordinates using Window Spy Click(23, 46) ; adjust this to your File menu position -Sleep(200) ; let the menu open +Sleep(100) +Click(23, 46) ; adjust this to your File menu position +Sleep(2000) ; let the menu open ;Navigate to “TFT Output” using arrow keys Send("{Down 1}") ; move to first item — adjust if needed diff --git a/docker/MQTTManager/CMakeLists.txt b/docker/MQTTManager/CMakeLists.txt index e659cf24..288d1f8e 100644 --- a/docker/MQTTManager/CMakeLists.txt +++ b/docker/MQTTManager/CMakeLists.txt @@ -3,11 +3,13 @@ project(nspm_mqttmanager) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_CXX_STANDARD 23) +set(TEST_MODE 0) set(CMAKE_INCLUDE_SRC_DIRECTORY /MQTTManager/include) set(PROTOBUF_SRC_DIRECTORY /MQTTManager/include/protobuf) add_compile_options(-rdynamic -g) add_compile_definitions(SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE BOOST_STACKTRACE_USE_BACKTRACE BOOST_BIND_GLOBAL_PLACEHOLDERS BOOST_STACKTRACE_LIBCXX_RUNTIME_MAY_CAUSE_MEMORY_LEAK) +add_compile_definitions(TEST_MODE=0) # Statically link libraries IF(DEFINED ENV{STRIP}) @@ -27,9 +29,12 @@ find_package(ixwebsocket REQUIRED) find_package(tz REQUIRED) find_package(protobuf REQUIRED) find_package(SqliteOrm REQUIRED) -# find_package(inja REQUIRED) +find_package(GTest REQUIRED) find_package(Boost REQUIRED COMPONENTS signals2 stacktrace_backtrace) +# Load CMake google test module +include(GoogleTest) + file(GLOB PROTOBUF_PB_CC_FILES "${CMAKE_INCLUDE_SRC_DIRECTORY}/protobuf/*.pb.cc") file(GLOB PROTOBUF_PB_H_FILES "${CMAKE_INCLUDE_SRC_DIRECTORY}/protobuf/*.pb.h") add_library(Protobuf_MQTTManager STATIC ${PROTOBUF_PB_CC_FILES} ${PROTOBUF_PB_H_FILES}) @@ -40,92 +45,102 @@ target_link_libraries(Protobuf_MQTTManager protobuf::protobuf) add_library(MQTTManager_WebHelper STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/web_helper/WebHelper.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/web_helper/WebHelper.hpp) set_target_properties(MQTTManager_WebHelper PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/WebHelper/web_helper.hpp) target_include_directories(MQTTManager_WebHelper PUBLIC ${CURL_INCLUDE_DIR} ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_WebHelper ${CURL_LIBRARIES} spdlog::spdlog Boost::boost) +target_link_libraries(MQTTManager_WebHelper ${CURL_LIBRARIES} spdlog::spdlog Boost::boost gtest::gtest) add_library(MQTTManager_DatabaseManager STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/database_manager/database_manager.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/database_manager/database_manager.hpp) # set_target_properties(MQTTManager_DatabaseManager PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/database_manager/database_manager.hpp) target_include_directories(MQTTManager_DatabaseManager PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_DatabaseManager PUBLIC spdlog::spdlog sqlite_orm::sqlite_orm nlohmann_json::nlohmann_json) +target_link_libraries(MQTTManager_DatabaseManager PUBLIC spdlog::spdlog sqlite_orm::sqlite_orm nlohmann_json::nlohmann_json gtest::gtest) add_library(MQTTManager_Entity STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/entity/entity.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/entity/entity.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/entity/entity_icons.hpp) set_target_properties(MQTTManager_Entity PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/entity/entity.hpp PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/entity/entity_icons.hpp) target_include_directories(MQTTManager_Entity PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_Entity spdlog::spdlog Boost::boost nlohmann_json::nlohmann_json) +target_link_libraries(MQTTManager_Entity spdlog::spdlog Boost::boost nlohmann_json::nlohmann_json gtest::gtest) + +add_library(MQTTManager_WebsocketServer STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/websocket_server/websocket_server.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/websocket_server/websocket_server.hpp) +set_target_properties(MQTTManager_WebsocketServer PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/websocket_server/websocket_server.hpp) +target_include_directories(MQTTManager_WebsocketServer PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) +target_link_libraries(MQTTManager_WebsocketServer ixwebsocket::ixwebsocket spdlog::spdlog nlohmann_json::nlohmann_json Boost::boost gtest::gtest) add_library(MQTT_Manager STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/mqtt_manager/mqtt_manager.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/mqtt_manager/mqtt_manager.hpp) set_target_properties(MQTT_Manager PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/mqtt_manager/mqtt_manager.hpp) target_include_directories(MQTT_Manager PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTT_Manager PahoMqttCpp::paho-mqttpp3-static MQTTManager_Config spdlog::spdlog Boost::boost protobuf::protobuf MQTTManager_WebsocketServer) +target_link_libraries(MQTT_Manager PahoMqttCpp::paho-mqttpp3-static MQTTManager_Config MQTTManager_WebsocketServer spdlog::spdlog Boost::boost protobuf::protobuf MQTTManager_WebsocketServer gtest::gtest) add_library(MQTTManager_CommandManager STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/command_manager/command_manager.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/command_manager/command_manager.hpp) set_target_properties(MQTTManager_CommandManager PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/command_manager/command_manager.hpp) target_include_directories(MQTTManager_CommandManager PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_CommandManager MQTT_Manager spdlog::spdlog Boost::boost Protobuf_MQTTManager nlohmann_json::nlohmann_json) - -add_library(MQTTManager_WebsocketServer STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/websocket_server/websocket_server.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/websocket_server/websocket_server.hpp) -set_target_properties(MQTTManager_WebsocketServer PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/websocket_server/websocket_server.hpp) -target_include_directories(MQTTManager_WebsocketServer PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_WebsocketServer ixwebsocket::ixwebsocket spdlog::spdlog nlohmann_json::nlohmann_json Boost::boost) +target_link_libraries(MQTTManager_CommandManager MQTT_Manager spdlog::spdlog Boost::boost Protobuf_MQTTManager nlohmann_json::nlohmann_json gtest::gtest) add_library(MQTTManager_HomeAssistantManager STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/home_assistant_manager/home_assistant_manager.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/home_assistant_manager/home_assistant_manager.hpp) set_target_properties(MQTTManager_HomeAssistantManager PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/home_assistant_manager/home_assistant_manager.hpp) target_include_directories(MQTTManager_HomeAssistantManager PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_HomeAssistantManager Boost::stacktrace_backtrace spdlog::spdlog nlohmann_json::nlohmann_json ixwebsocket::ixwebsocket Boost::boost Boost::stacktrace_backtrace MQTTManager_Config MQTTManager_WebsocketServer) +target_link_libraries(MQTTManager_HomeAssistantManager Boost::stacktrace_backtrace spdlog::spdlog nlohmann_json::nlohmann_json ixwebsocket::ixwebsocket Boost::boost Boost::stacktrace_backtrace MQTTManager_Config MQTTManager_WebsocketServer gtest::gtest) add_library(MQTTManager_OpenhabManager STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/openhab_manager/openhab_manager.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/openhab_manager/openhab_manager.hpp) set_target_properties(MQTTManager_OpenhabManager PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/openhab_manager/openhab_manager.hpp) target_include_directories(MQTTManager_OpenhabManager PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_OpenhabManager Boost::stacktrace_backtrace spdlog::spdlog nlohmann_json::nlohmann_json ixwebsocket::ixwebsocket Boost::boost MQTTManager_Config MQTTManager_WebsocketServer) +target_link_libraries(MQTTManager_OpenhabManager Boost::stacktrace_backtrace spdlog::spdlog nlohmann_json::nlohmann_json ixwebsocket::ixwebsocket Boost::boost MQTTManager_Config MQTTManager_WebsocketServer gtest::gtest) add_library(MQTTManager_Weather STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/weather/weather.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/weather/weather.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/weather/weather_icons.hpp) set_target_properties(MQTTManager_Weather PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/weather/weather.hpp) target_include_directories(MQTTManager_Weather PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_Weather MQTTManager_WebHelper spdlog::spdlog nlohmann_json::nlohmann_json MQTTManager_Config MQTTManager_HomeAssistantManager MQTTManager_OpenhabManager MQTT_Manager) +target_link_libraries(MQTTManager_Weather MQTTManager_WebHelper spdlog::spdlog nlohmann_json::nlohmann_json MQTTManager_Config MQTTManager_HomeAssistantManager MQTTManager_OpenhabManager MQTT_Manager gtest::gtest) -add_library(MQTTManager_Light STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/light.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/light.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/home_assistant_light.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/home_assistant_light.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/openhab_light.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/openhab_light.hpp) +add_library(MQTTManager_Light STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/light.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/light.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/home_assistant_light.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/home_assistant_light.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/openhab_light.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/openhab_light.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/openhab_light.cpp) set_target_properties(MQTTManager_Light PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/light.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/home_assistant_light.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/light/openhab_light.hpp) target_include_directories(MQTTManager_Light PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_Light MQTTManager_HomeAssistantManager spdlog::spdlog nlohmann_json::nlohmann_json MQTT_Manager Boost::boost MQTTManager_Entity Protobuf_MQTTManager MQTTManager_CommandManager) +target_link_libraries(MQTTManager_Light MQTTManager_HomeAssistantManager spdlog::spdlog nlohmann_json::nlohmann_json MQTT_Manager Boost::boost MQTTManager_Entity Protobuf_MQTTManager MQTTManager_CommandManager gtest::gtest) add_library(MQTTManager_Button STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/button/button.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/button/button.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/button/home_assistant_button.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/button/home_assistant_button.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/button/nspm_button.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/button/nspm_button.hpp) set_target_properties(MQTTManager_Button PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/button/button.hpp) target_include_directories(MQTTManager_Button PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_Button MQTTManager_HomeAssistantManager spdlog::spdlog nlohmann_json::nlohmann_json MQTT_Manager Boost::boost MQTTManager_Entity Protobuf_MQTTManager MQTTManager_CommandManager) +target_link_libraries(MQTTManager_Button MQTTManager_HomeAssistantManager spdlog::spdlog nlohmann_json::nlohmann_json MQTT_Manager Boost::boost MQTTManager_Entity Protobuf_MQTTManager MQTTManager_CommandManager gtest::gtest) + +add_library(MQTTManager_Thermostat STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/thermostat/thermostat.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/thermostat/thermostat.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/thermostat/home_assistant_thermostat.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/thermostat/home_assistant_thermostat.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/thermostat/openhab_thermostat.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/thermostat/openhab_thermostat.cpp) +set_target_properties(MQTTManager_Thermostat PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/thermostat/thermostat.hpp) +target_include_directories(MQTTManager_Thermostat PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) +target_link_libraries(MQTTManager_Thermostat MQTTManager_HomeAssistantManager MQTTManager_OpenhabManager spdlog::spdlog nlohmann_json::nlohmann_json MQTT_Manager Boost::boost MQTTManager_Entity Protobuf_MQTTManager MQTTManager_CommandManager gtest::gtest) add_library(MQTTManager_Switch STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/switch/switch.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/switch/switch.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/switch/home_assistant_switch.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/switch/home_assistant_switch.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/switch/openhab_switch.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/switch/openhab_switch.hpp) set_target_properties(MQTTManager_Switch PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/switch/switch.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/switch/home_assistant_switch.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/switch/openhab_switch.hpp) target_include_directories(MQTTManager_Switch PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_Switch MQTTManager_HomeAssistantManager spdlog::spdlog nlohmann_json::nlohmann_json MQTT_Manager Boost::boost MQTTManager_Entity Protobuf_MQTTManager MQTTManager_CommandManager) +target_link_libraries(MQTTManager_Switch MQTTManager_HomeAssistantManager spdlog::spdlog nlohmann_json::nlohmann_json MQTT_Manager Boost::boost MQTTManager_Entity Protobuf_MQTTManager MQTTManager_CommandManager gtest::gtest) add_library(MQTTManager_NSPanel STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/nspanel/nspanel.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/nspanel/nspanel.hpp) set_target_properties(MQTTManager_NSPanel PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/nspanel/nspanel.hpp) target_include_directories(MQTTManager_NSPanel PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_NSPanel spdlog::spdlog nlohmann_json::nlohmann_json MQTT_Manager MQTTManager_Room MQTTManager_WebsocketServer tz::tz Boost::boost MQTTManager_Config Protobuf_MQTTManager MQTTManager_DatabaseManager) +target_link_libraries(MQTTManager_NSPanel spdlog::spdlog nlohmann_json::nlohmann_json MQTT_Manager MQTTManager_Room MQTTManager_WebsocketServer tz::tz Boost::boost MQTTManager_Config Protobuf_MQTTManager MQTTManager_DatabaseManager gtest::gtest) add_library(MQTTManager_Scene STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/scenes/scene.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/scenes/scene.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/scenes/nspm_scene.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/scenes/nspm_scene.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/scenes/home_assistant_scene.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/scenes/home_assistant_scene.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/scenes/openhab_scene.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/scenes/openhab_scene.cpp) # set_target_properties(MQTTManager_Scene PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/scene/scene.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/scene/nspm_scene.hpp) target_include_directories(MQTTManager_Scene PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_Scene spdlog::spdlog nlohmann_json::nlohmann_json MQTT_Manager MQTTManager_Room MQTTManager_Entity MQTTManager_Light MQTTManager_HomeAssistantManager MQTTManager_OpenhabManager) +target_link_libraries(MQTTManager_Scene spdlog::spdlog nlohmann_json::nlohmann_json MQTT_Manager MQTTManager_Room MQTTManager_Entity MQTTManager_Light MQTTManager_HomeAssistantManager MQTTManager_OpenhabManager gtest::gtest) add_library(MQTTManager_RoomEntitiesPage STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/room/room_entities_page.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/room/room_entities_page.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/room/room_entities_page.cpp) target_include_directories(MQTTManager_RoomEntitiesPage PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_RoomEntitiesPage Boost::boost spdlog::spdlog nlohmann_json::nlohmann_json Protobuf_MQTTManager MQTTManager_WebHelper MQTTManager_Light MQTTManager_Switch MQTTManager_Scene MQTTManager_DatabaseManager) +target_link_libraries(MQTTManager_RoomEntitiesPage Boost::boost spdlog::spdlog nlohmann_json::nlohmann_json Protobuf_MQTTManager MQTTManager_WebHelper MQTTManager_Light MQTTManager_Switch MQTTManager_Scene MQTTManager_DatabaseManager gtest::gtest) add_library(MQTTManager_Room STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/room/room.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/room/room.hpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/room/room.cpp) target_include_directories(MQTTManager_Room PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_Room MQTTManager_RoomEntitiesPage spdlog::spdlog nlohmann_json::nlohmann_json MQTTManager_Entity MQTTManager_Light MQTTManager_Switch Protobuf_MQTTManager MQTT_Manager MQTTManager_DatabaseManager) +target_link_libraries(MQTTManager_Room MQTTManager_RoomEntitiesPage spdlog::spdlog nlohmann_json::nlohmann_json MQTTManager_Entity MQTTManager_Light MQTTManager_Switch Protobuf_MQTTManager MQTT_Manager MQTTManager_DatabaseManager gtest::gtest) add_library(MQTTManager_EntityManager STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/entity_manager/entity_manager.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/entity_manager/entity_manager.hpp) set_target_properties(MQTTManager_EntityManager PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/entity_manager/entity_manager.hpp) target_include_directories(MQTTManager_EntityManager PUBLIC MQTTManager_WebHelper ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_EntityManager MQTTManager_WebHelper spdlog::spdlog MQTT_Manager MQTTManager_NSPanel MQTTManager_Light MQTTManager_Button MQTTManager_Switch MQTTManager_WebsocketServer Boost::boost Boost::stacktrace_backtrace dl MQTTManager_Weather) +target_link_libraries(MQTTManager_EntityManager MQTTManager_WebHelper spdlog::spdlog MQTT_Manager MQTTManager_NSPanel MQTTManager_Light MQTTManager_Button MQTTManager_Thermostat MQTTManager_Switch MQTTManager_WebsocketServer Boost::boost Boost::stacktrace_backtrace dl MQTTManager_Weather gtest::gtest) add_library(MQTTManager_Config STATIC ${CMAKE_INCLUDE_SRC_DIRECTORY}/mqtt_manager_config/mqtt_manager_config.cpp ${CMAKE_INCLUDE_SRC_DIRECTORY}/mqtt_manager_config/mqtt_manager_config.hpp) # set_target_properties(MQTTManager_Config PROPERTIES PUBLIC_HEADER ${CMAKE_INCLUDE_SRC_DIRECTORY}/mqtt_manager_config/mqtt_manager_config.hpp) target_include_directories(MQTTManager_Config PUBLIC MQTTManager_WebHelper ${CMAKE_INCLUDE_SRC_DIRECTORY} ${PROTOBUF_SRC_DIRECTORY}) -target_link_libraries(MQTTManager_Config spdlog::spdlog MQTTManager_WebHelper nlohmann_json::nlohmann_json Boost::boost Protobuf_MQTTManager MQTTManager_DatabaseManager MQTTManager_WebsocketServer) +target_link_libraries(MQTTManager_Config spdlog::spdlog MQTTManager_WebHelper nlohmann_json::nlohmann_json Boost::boost Protobuf_MQTTManager MQTTManager_DatabaseManager MQTTManager_WebsocketServer gtest::gtest) add_executable(${PROJECT_NAME} src/main.cpp) target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_INCLUDE_SRC_DIRECTORY}) -target_link_libraries(${PROJECT_NAME} MQTT_Manager MQTTManager_Config MQTTManager_HomeAssistantManager MQTTManager_OpenhabManager MQTTManager_EntityManager MQTTManager_Scene MQTTManager_Room MQTTManager_WebsocketServer MQTTManager_CommandManager) +target_link_libraries(${PROJECT_NAME} MQTT_Manager MQTTManager_Config MQTTManager_HomeAssistantManager MQTTManager_OpenhabManager MQTTManager_EntityManager MQTTManager_Scene MQTTManager_Room MQTTManager_WebsocketServer MQTTManager_CommandManager gtest::gtest) + +if (TEST_MODE==1) + enable_testing() + gtest_discover_tests(${PROJECT_NAME}) +endif() diff --git a/docker/MQTTManager/CMakeUserPresets.json b/docker/MQTTManager/CMakeUserPresets.json index 7b6a139f..32e012fb 100644 --- a/docker/MQTTManager/CMakeUserPresets.json +++ b/docker/MQTTManager/CMakeUserPresets.json @@ -4,7 +4,6 @@ "conan": {} }, "include": [ - "build/build/Debug/generators/CMakePresets.json", "build/Debug/generators/CMakePresets.json" ] } \ No newline at end of file diff --git a/docker/MQTTManager/compile_mqttmanager.sh b/docker/MQTTManager/compile_mqttmanager.sh index a987094c..00fd1936 100755 --- a/docker/MQTTManager/compile_mqttmanager.sh +++ b/docker/MQTTManager/compile_mqttmanager.sh @@ -3,6 +3,7 @@ echo "Starting to compile MQTTManager." TARGETPLATFORM="" STRIP="" +TEST_MODE=0 while true; do case "$1" in @@ -17,10 +18,24 @@ while true; do export STRIP="1" shift ;; + --test) + echo "Will compile for testing. Removing old test DB (if any)." + rm -f /tmp/nspanelmanager_db_test.sqlite3 + TEST_MODE=1 + shift + ;; *) break ;; esac done +if [ "$TEST_MODE" -eq 1 ]; then + sed -i 's/set(TEST_MODE.*/set(TEST_MODE 1)/g' /MQTTManager/CMakeLists.txt + sed -i 's/add_compile_definitions(TEST_MODE.*/add_compile_definitions(TEST_MODE=1)/g' /MQTTManager/CMakeLists.txt +else + sed -i 's/set(TEST_MODE.*/set(TEST_MODE 0)/g' /MQTTManager/CMakeLists.txt + sed -i 's/add_compile_definitions(TEST_MODE.*/add_compile_definitions(TEST_MODE=0)/g' /MQTTManager/CMakeLists.txt +fi + if [ -z "$TARGETPLATFORM" ]; then TARGETPLATFORM="linux/$(dpkg-architecture -q DEB_BUILD_ARCH_CPU)" echo "No platform given as argument, will assume $TARGETPLATFORM" @@ -126,7 +141,9 @@ cd build source $BUILD_TYPE/generators/conanbuild.sh cmake .. -DCMAKE_TOOLCHAIN_FILE=$BUILD_TYPE/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS} cmake --build . --config $BUILD_TYPE -j $(nproc) -sed -i "s|/MQTTManager/|/home/tim/NSPanelManager/docker/MQTTManager/|g" compile_commands.json +if [ -f /MQTTManager/fix_compile_commands_path.sh ]; then + source /MQTTManager/fix_compile_commands_path.sh +fi cp compile_commands.json ../ if [ "$STRIP" == "1" ]; then diff --git a/docker/MQTTManager/conanfile.py b/docker/MQTTManager/conanfile.py index 7dfa2b5a..2e78f128 100644 --- a/docker/MQTTManager/conanfile.py +++ b/docker/MQTTManager/conanfile.py @@ -19,6 +19,8 @@ def requirements(self): self.requires("boost/1.84.0") self.requires("protobuf/5.27.0") self.requires("sqlite_orm/1.9.1") + # Testing tools: + self.requires("gtest/1.16.0") def configure(self): self.options["boost"].without_stacktrace = False diff --git a/docker/MQTTManager/fix_compile_commands_path.sh.example b/docker/MQTTManager/fix_compile_commands_path.sh.example new file mode 100644 index 00000000..3608a8bf --- /dev/null +++ b/docker/MQTTManager/fix_compile_commands_path.sh.example @@ -0,0 +1,5 @@ +#!/bin/bash +# This file is used to fix the compile_commands.json file path for MQTTManager so that the +# host computer IDE can find associated libraries. Copy this file to /MQTTManager/fix_compile_commands_path.sh +# and it will be automatically sourced when building MQTTManager. +sed -i "s|/MQTTManager/|/home/tim/NSPanelManager/docker/MQTTManager/|g" compile_commands.json diff --git a/docker/MQTTManager/include/button/home_assistant_button.cpp b/docker/MQTTManager/include/button/home_assistant_button.cpp index 79c1c63f..12779516 100644 --- a/docker/MQTTManager/include/button/home_assistant_button.cpp +++ b/docker/MQTTManager/include/button/home_assistant_button.cpp @@ -47,7 +47,7 @@ void HomeAssistantButton::send_state_update_to_controller() { } HomeAssistantManager::send_json(service_data); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_entity_changed_callbacks(this); } } diff --git a/docker/MQTTManager/include/command_manager/command_manager.cpp b/docker/MQTTManager/include/command_manager/command_manager.cpp index 00b6f06b..73245228 100644 --- a/docker/MQTTManager/include/command_manager/command_manager.cpp +++ b/docker/MQTTManager/include/command_manager/command_manager.cpp @@ -8,7 +8,7 @@ #include void CommandManager::init() { - MQTT_Manager::subscribe(fmt::format("nspanel/mqttmanager_{}/command", MqttManagerConfig::get_settings().manager_address), &CommandManager::process_command); + MQTT_Manager::subscribe(fmt::format("nspanel/mqttmanager_{}/command", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)), &CommandManager::process_command); SPDLOG_INFO("CommandManager initialized."); } diff --git a/docker/MQTTManager/include/database_manager/database_manager.cpp b/docker/MQTTManager/include/database_manager/database_manager.cpp index 2703884b..4498038a 100644 --- a/docker/MQTTManager/include/database_manager/database_manager.cpp +++ b/docker/MQTTManager/include/database_manager/database_manager.cpp @@ -1,4 +1,5 @@ #include +#include // This file is strictly here to make CMake understand that this is a C++ library void database_manager::Entity::set_entity_data_json(const nlohmann::json &json) { diff --git a/docker/MQTTManager/include/database_manager/database_manager.hpp b/docker/MQTTManager/include/database_manager/database_manager.hpp index 9f2921ee..63772700 100644 --- a/docker/MQTTManager/include/database_manager/database_manager.hpp +++ b/docker/MQTTManager/include/database_manager/database_manager.hpp @@ -1,7 +1,4 @@ #pragma once -#include "nlohmann/json.hpp" -#include -#include #include #include #include @@ -31,11 +28,12 @@ struct NSPanel { int id = 0; std::string mac_address; std::string friendly_name; + std::string model; int room_id = 0; std::string version; - std::optional button1_detached_mode_light_id = 0; + std::optional button1_detached_mode_entity_id = 0; int button1_mode = 0; - std::optional button2_detached_mode_light_id = 0; + std::optional button2_detached_mode_entity_id = 0; int button2_mode = 0; std::string md5_data_file; std::string md5_firmware; @@ -105,7 +103,11 @@ struct Entity { void set_entity_data_json(const nlohmann::json &json); }; +#if defined(TEST_MODE) && TEST_MODE == 1 +static inline auto database = sqlite_orm::make_storage("/tmp/nspanelmanager_db_test.sqlite3", +#else static inline auto database = sqlite_orm::make_storage("/data/nspanelmanager_db.sqlite3", +#endif sqlite_orm::make_table("web_roomentitiespage", sqlite_orm::make_column("id", &RoomEntitiesPage::id, sqlite_orm::primary_key().autoincrement()), sqlite_orm::make_column("display_order", &RoomEntitiesPage::display_order), @@ -125,11 +127,12 @@ static inline auto database = sqlite_orm::make_storage("/data/nspanelmanager_db. sqlite_orm::make_column("id", &NSPanel::id, sqlite_orm::primary_key().autoincrement()), sqlite_orm::make_column("friendly_name", &NSPanel::friendly_name), sqlite_orm::make_column("mac_address", &NSPanel::mac_address), + sqlite_orm::make_column("model", &NSPanel::model), sqlite_orm::make_column("room_id", &NSPanel::room_id), sqlite_orm::make_column("version", &NSPanel::version), - sqlite_orm::make_column("button1_detached_mode_light_id", &NSPanel::button1_detached_mode_light_id), + sqlite_orm::make_column("button1_detached_mode_entity_id", &NSPanel::button1_detached_mode_entity_id), sqlite_orm::make_column("button1_mode", &NSPanel::button1_mode), - sqlite_orm::make_column("button2_detached_mode_light_id", &NSPanel::button2_detached_mode_light_id), + sqlite_orm::make_column("button2_detached_mode_entity_id", &NSPanel::button2_detached_mode_entity_id), sqlite_orm::make_column("button2_mode", &NSPanel::button2_mode), sqlite_orm::make_column("md5_data_file", &NSPanel::md5_data_file), sqlite_orm::make_column("md5_firmware", &NSPanel::md5_firmware), @@ -174,7 +177,36 @@ static inline auto database = sqlite_orm::make_storage("/data/nspanelmanager_db. sqlite_orm::make_column("room_id", &Entity::room_id))); static void init() { + +#if defined(TEST_MODE) && TEST_MODE == 1 + SPDLOG_INFO("Running database /tmp/nspanelmanager_db_test.sqlite3 as it's in TEST_MODE. Syncing schema..."); + auto result = database_manager::database.sync_schema(); + SPDLOG_INFO("Sync schema result:"); + for (auto &n : result) { + switch (n.second) { + case sqlite_orm::sync_schema_result::new_table_created: + SPDLOG_INFO("Created table {}", n.first); + break; + case sqlite_orm::sync_schema_result::already_in_sync: + SPDLOG_INFO("Table {} already in sync", n.first); + break; + case sqlite_orm::sync_schema_result::old_columns_removed: + SPDLOG_INFO("Removed old columns from table {}", n.first); + break; + case sqlite_orm::sync_schema_result::new_columns_added: + SPDLOG_INFO("Added new columns to table {}", n.first); + break; + case sqlite_orm::sync_schema_result::new_columns_added_and_old_columns_removed: + SPDLOG_INFO("Added new columns and removed old columns from table {}", n.first); + break; + case sqlite_orm::sync_schema_result::dropped_and_recreated: + SPDLOG_INFO("Dropped and recreated table {}", n.first); + break; + } + } +#else database_manager::database.open_forever(); +#endif } }; // namespace database_manager diff --git a/docker/MQTTManager/include/entity/entity.hpp b/docker/MQTTManager/include/entity/entity.hpp index 0a418703..4ce99165 100644 --- a/docker/MQTTManager/include/entity/entity.hpp +++ b/docker/MQTTManager/include/entity/entity.hpp @@ -6,9 +6,11 @@ #include #include enum MQTT_MANAGER_ENTITY_TYPE { + ANY, LIGHT, SWITCH_ENTITY, BUTTON, + THERMOSTAT, SCENE, }; diff --git a/docker/MQTTManager/include/entity/entity_icons.hpp b/docker/MQTTManager/include/entity/entity_icons.hpp index 813d0057..50d454b4 100644 --- a/docker/MQTTManager/include/entity/entity_icons.hpp +++ b/docker/MQTTManager/include/entity/entity_icons.hpp @@ -14,7 +14,7 @@ class EntityIcons { // Thermostat Icons static constexpr const char *heating = "!"; - static constexpr const char *cooling = "\""; + static constexpr const char *cooling = "8"; // Was originally " but that causes issues when sending the .txt command to Nextion. static constexpr const char *hot_cold = "#"; static constexpr const char *thermostat_auto = "$"; static constexpr const char *dry = "%"; @@ -35,6 +35,8 @@ class EntityIcons { static constexpr const char *fan1 = "4"; static constexpr const char *fan2 = "5"; static constexpr const char *fan3 = "6"; + static constexpr const char *thermostat = "7"; + static constexpr const char *OFF = "h"; }; class GUI_Colors { diff --git a/docker/MQTTManager/include/entity_manager/entity_manager.cpp b/docker/MQTTManager/include/entity_manager/entity_manager.cpp index af8b9ac3..983a442c 100644 --- a/docker/MQTTManager/include/entity_manager/entity_manager.cpp +++ b/docker/MQTTManager/include/entity_manager/entity_manager.cpp @@ -16,6 +16,8 @@ #include "scenes/openhab_scene.hpp" #include "scenes/scene.hpp" #include "switch/switch.hpp" +#include "thermostat/home_assistant_thermostat.hpp" +#include "thermostat/openhab_thermostat.hpp" #include "web_helper/WebHelper.hpp" #include "websocket_server/websocket_server.hpp" #include @@ -48,6 +50,7 @@ #include #include #include +#include #include #include @@ -84,6 +87,7 @@ void EntityManager::load_entities() { EntityManager::load_lights(); EntityManager::load_buttons(); + EntityManager::load_thermostats(); EntityManager::load_switches(); EntityManager::load_scenes(); EntityManager::load_global_room_entities_pages(); @@ -253,6 +257,53 @@ void EntityManager::load_buttons() { SPDLOG_DEBUG("Loaded {} lights", button_ids.size()); } +void EntityManager::load_thermostats() { + auto thermostat_ids = database_manager::database.select(&database_manager::Entity::id, sqlite_orm::from(), + sqlite_orm::where(sqlite_orm::glob(&database_manager::Entity::entity_type, "thermostat"))); + SPDLOG_INFO("Loading {} thermostats.", thermostat_ids.size()); + + // Check if any existing thermostat has been removed. + EntityManager::_entities.erase(std::remove_if(EntityManager::_entities.begin(), EntityManager::_entities.end(), [&thermostat_ids](auto entity) { + return entity->get_type() == MQTT_MANAGER_ENTITY_TYPE::THERMOSTAT && std::find_if(thermostat_ids.begin(), thermostat_ids.end(), [&entity](auto id) { return id == entity->get_id(); }) == thermostat_ids.end(); + }), + EntityManager::_entities.end()); + + // Cause existing thermostats to reload config or add a new thermostat if it does not exist. + for (auto &thermostat_id : thermostat_ids) { + auto existing_thermostat = EntityManager::get_entity_by_id(MQTT_MANAGER_ENTITY_TYPE::THERMOSTAT, thermostat_id); + if (existing_thermostat) [[likely]] { + (*existing_thermostat)->reload_config(); + } else { + std::lock_guard mutex_guard(EntityManager::_entities_mutex); + + try { + auto thermostat_settings = database_manager::database.get(thermostat_id); + nlohmann::json entity_data = thermostat_settings.get_entity_data_json(); + if (entity_data.contains("controller")) { + std::string controller = entity_data["controller"]; + if (controller.compare("home_assistant") == 0) { + std::shared_ptr thermostat_entity = std::shared_ptr(new HomeAssistantThermostat(thermostat_settings.id)); + SPDLOG_INFO("Thermostat {}::{} was found in database but not in config. Creating thermostat.", thermostat_entity->get_id(), thermostat_entity->get_name()); + EntityManager::_entities.push_back(thermostat_entity); + } else if (controller.compare("openhab") == 0) { + std::shared_ptr thermostat_entity = std::shared_ptr(new OpenhabThermostat(thermostat_settings.id)); + SPDLOG_INFO("Thermostat {}::{} was found in database but not in config. Creating thermostat.", thermostat_entity->get_id(), thermostat_entity->get_name()); + EntityManager::_entities.push_back(thermostat_entity); + } else { + SPDLOG_ERROR("Unknown thermostat type '{}'. Will ignore entity.", controller); + } + } else { + SPDLOG_ERROR("Thermostat {}::{} does not define a controller!", thermostat_settings.id, thermostat_settings.friendly_name); + } + } catch (std::exception &e) { + SPDLOG_ERROR("Caught exception: {}", e.what()); + SPDLOG_ERROR("Stacktrace: {}", boost::stacktrace::to_string(boost::stacktrace::stacktrace())); + } + } + } + SPDLOG_DEBUG("Loaded {} thermostats", thermostat_ids.size()); +} + void EntityManager::load_switches() { auto switch_ids = database_manager::database.select(&database_manager::Entity::id, sqlite_orm::from(), sqlite_orm::where(sqlite_orm::glob(&database_manager::Entity::entity_type, "switch"))); @@ -316,21 +367,23 @@ void EntityManager::load_scenes() { if (existing_scene) [[likely]] { (*existing_scene)->reload_config(); } else { - std::lock_guard mutex_guard(EntityManager::_entities_mutex); try { auto scene_settings = database_manager::database.get(scene_id); if (scene_settings.scene_type.compare("home_assistant") == 0) { std::shared_ptr scene = std::shared_ptr(new HomeAssistantScene(scene_settings.id)); SPDLOG_INFO("Scene {}::{} was found in database but not in config. Creating scene.", scene->get_id(), scene->get_name()); + std::lock_guard mutex_guard(EntityManager::_entities_mutex); EntityManager::_entities.push_back(scene); } else if (scene_settings.scene_type.compare("openhab") == 0) { std::shared_ptr scene = std::shared_ptr(new OpenhabScene(scene_settings.id)); SPDLOG_INFO("Scene {}::{} was found in database but not in config. Creating scene.", scene->get_id(), scene->get_name()); + std::lock_guard mutex_guard(EntityManager::_entities_mutex); EntityManager::_entities.push_back(scene); } else if (scene_settings.scene_type.compare("nspm_scene") == 0) { std::shared_ptr scene = std::shared_ptr(new NSPMScene(scene_settings.id)); SPDLOG_INFO("Scene {}::{} was found in database but not in config. Creating scene.", scene->get_id(), scene->get_name()); + std::lock_guard mutex_guard(EntityManager::_entities_mutex); EntityManager::_entities.push_back(scene); } else { SPDLOG_ERROR("Unknown scene type '{}'. Will ignore entity.", scene_settings.scene_type); @@ -440,7 +493,7 @@ void EntityManager::update_all_rooms_status() { }); } // Wait until changes has settled as when a user changes light states in "All rooms" mode a burst of changes will occur from all rooms. - uint32_t backoff_time = std::stoi(MqttManagerConfig::get_setting_with_default("all_rooms_status_backoff_time", "250")); + uint32_t backoff_time = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::ALL_ROOMS_STATUS_BACKOFF_TIME); while (EntityManager::_last_room_update_time.load() + std::chrono::milliseconds(backoff_time) > std::chrono::system_clock::now()) { std::this_thread::sleep_for(EntityManager::_last_room_update_time.load() + std::chrono::milliseconds(backoff_time) - std::chrono::system_clock::now()); } @@ -529,9 +582,9 @@ void EntityManager::update_all_rooms_status() { if (num_kelvin_lights_total > 0) { float average_kelvin = (float)total_kelvin_level_all / num_kelvin_lights_total; - average_kelvin -= MqttManagerConfig::get_settings().color_temp_min; - uint8_t kelvin_pct = (average_kelvin / (MqttManagerConfig::get_settings().color_temp_max - MqttManagerConfig::get_settings().color_temp_min)) * 100; - if (MqttManagerConfig::get_settings().reverse_color_temperature_slider) { + average_kelvin -= MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint8_t kelvin_pct = (average_kelvin / (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX) - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN))) * 100; + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::REVERSE_COLOR_TEMP)) { kelvin_pct = 100 - kelvin_pct; } all_rooms_status.set_average_color_temperature(kelvin_pct); @@ -548,9 +601,9 @@ void EntityManager::update_all_rooms_status() { if (num_kelvin_lights_table > 0) { float average_kelvin = (float)total_kelvin_table / num_kelvin_lights_table; - average_kelvin -= MqttManagerConfig::get_settings().color_temp_min; - uint8_t kelvin_pct = (average_kelvin / (MqttManagerConfig::get_settings().color_temp_max - MqttManagerConfig::get_settings().color_temp_min)) * 100; - if (MqttManagerConfig::get_settings().reverse_color_temperature_slider) { + average_kelvin -= MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint8_t kelvin_pct = (average_kelvin / (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX) - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN))) * 100; + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::REVERSE_COLOR_TEMP)) { kelvin_pct = 100 - kelvin_pct; } @@ -568,10 +621,10 @@ void EntityManager::update_all_rooms_status() { all_rooms_status.set_ceiling_lights_dim_level(total_light_level_ceiling / num_lights_ceiling_on); if (num_kelvin_lights_ceiling > 0) { - float average_kelvin = (float)total_kelvin_ceiling / num_kelvin_lights_ceiling; - average_kelvin -= MqttManagerConfig::get_settings().color_temp_min; - uint8_t kelvin_pct = (average_kelvin / (MqttManagerConfig::get_settings().color_temp_max - MqttManagerConfig::get_settings().color_temp_min)) * 100; - if (MqttManagerConfig::get_settings().reverse_color_temperature_slider) { + float average_kelvin = (float)total_kelvin_table / num_kelvin_lights_table; + average_kelvin -= MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint8_t kelvin_pct = (average_kelvin / (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX) - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN))) * 100; + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::REVERSE_COLOR_TEMP)) { kelvin_pct = 100 - kelvin_pct; } @@ -588,7 +641,7 @@ void EntityManager::update_all_rooms_status() { std::string all_rooms_status_string; if (all_rooms_status.SerializeToString(&all_rooms_status_string)) { SPDLOG_DEBUG("All rooms status updated. Waiting for next notify."); - MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/all_rooms_status", MqttManagerConfig::get_settings().manager_address), all_rooms_status_string, true); + MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/all_rooms_status", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)), all_rooms_status_string, true); has_performed_initial_update = true; } else { SPDLOG_ERROR("Failed to serialize 'All rooms' status. Will try again next time there is a room status change."); @@ -717,7 +770,7 @@ void EntityManager::_command_callback(NSPanelMQTTManagerCommand &command) { uint16_t average_light_brightness = total_light_brightness / room_entities.size(); if (average_light_brightness == 0) { - average_light_brightness = std::stoi(MqttManagerConfig::get_setting_with_default("light_turn_on_brightness", "50")); + average_light_brightness = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::LIGHT_TURN_ON_BRIGHTNESS); } light_entity->set_brightness(average_light_brightness, false); diff --git a/docker/MQTTManager/include/entity_manager/entity_manager.hpp b/docker/MQTTManager/include/entity_manager/entity_manager.hpp index 0ced2d4f..6153c2aa 100644 --- a/docker/MQTTManager/include/entity_manager/entity_manager.hpp +++ b/docker/MQTTManager/include/entity_manager/entity_manager.hpp @@ -76,6 +76,11 @@ class EntityManager { */ static void load_buttons(); + /* + * Load all thermostats from the DB and remove any existing thermostat that no longer exist. + */ + static void load_thermostats(); + /* * Load all switches from the DB and remove any existing switch that no longer exist. */ @@ -132,11 +137,14 @@ class EntityManager { std::lock_guard mutex_guard(EntityManager::_entities_mutex); auto rit = EntityManager::_entities.cbegin(); while (rit != EntityManager::_entities.cend()) { - if ((*rit)->get_type() == type && (*rit)->get_id() == id) { - return std::static_pointer_cast(*rit); - } else { - ++rit; + if ((*rit)->get_id() == id) { + if (type == MQTT_MANAGER_ENTITY_TYPE::ANY) { + return std::static_pointer_cast(*rit); + } else if ((*rit)->get_type() == type) { + return std::static_pointer_cast(*rit); + } } + ++rit; } return std::unexpected(EntityError::NOT_FOUND); } diff --git a/docker/MQTTManager/include/home_assistant_manager/home_assistant_manager.cpp b/docker/MQTTManager/include/home_assistant_manager/home_assistant_manager.cpp index dbfb47ae..03d49eb2 100644 --- a/docker/MQTTManager/include/home_assistant_manager/home_assistant_manager.cpp +++ b/docker/MQTTManager/include/home_assistant_manager/home_assistant_manager.cpp @@ -49,7 +49,7 @@ void HomeAssistantManager::connect() { WebsocketServer::register_warning(WebsocketServer::ActiveWarningLevel::ERROR, "Home Assistant not connected."); - if (MqttManagerConfig::get_settings().is_home_assistant_addon) { + if (MqttManagerConfig::is_home_assistant_addon()) { home_assistant_websocket_url.append("/core/websocket"); } else { home_assistant_websocket_url.append("/api/websocket"); @@ -81,8 +81,8 @@ void HomeAssistantManager::reload_config() { bool reconnect = false; { std::lock_guard lock_guard(HomeAssistantManager::_settings_mutex); - std::string address = MqttManagerConfig::get_setting_with_default("home_assistant_address", ""); - std::string token = MqttManagerConfig::get_setting_with_default("home_assistant_token", ""); + std::string address = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::HOME_ASSISTANT_ADDRESS); + std::string token = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::HOME_ASSISTANT_TOKEN); if (HomeAssistantManager::_home_assistant_address.compare(address) != 0 || HomeAssistantManager::_home_assistant_token.compare(token) != 0) { HomeAssistantManager::_home_assistant_address = address; @@ -189,7 +189,11 @@ void HomeAssistantManager::_send_string(std::string &data) { if (HomeAssistantManager::_websocket != nullptr && HomeAssistantManager::_connected) { std::lock_guard mtex_lock(HomeAssistantManager::_mutex_websocket_write_access); SPDLOG_TRACE("[HA WS] Sending data: {}", data); - HomeAssistantManager::_websocket->send(data); + try { + HomeAssistantManager::_websocket->send(data); + } catch (std::exception &e) { + SPDLOG_ERROR("[HA WS] Send failed. Caught error: {}", e.what()); + } } } @@ -208,6 +212,7 @@ void HomeAssistantManager::_process_home_assistant_event(nlohmann::json &event_d std::string home_assistant_entity_name = event_data["event"]["data"]["entity_id"]; if (HomeAssistantManager::_home_assistant_observers.find(home_assistant_entity_name) != HomeAssistantManager::_home_assistant_observers.end()) { try { + SPDLOG_TRACE("HA Event: {}", event_data.dump()); HomeAssistantManager::_home_assistant_observers.at(home_assistant_entity_name)(event_data); } catch (const std::exception &e) { SPDLOG_ERROR("Caught exception during processing of home assistant event. Diagnostic information: {}", boost::diagnostic_information(e, true)); diff --git a/docker/MQTTManager/include/light/home_assistant_light.cpp b/docker/MQTTManager/include/light/home_assistant_light.cpp index 6eeafd1f..4ff3d13e 100644 --- a/docker/MQTTManager/include/light/home_assistant_light.cpp +++ b/docker/MQTTManager/include/light/home_assistant_light.cpp @@ -2,28 +2,47 @@ #include "database_manager/database_manager.hpp" #include "entity/entity.hpp" #include "light/light.hpp" -#include "mqtt_manager/mqtt_manager.hpp" #include "mqtt_manager_config/mqtt_manager_config.hpp" -#include "protobuf_general.pb.h" +#include "protobuf_nspanel.pb.h" #include #include -#include #include +#include #include #include +#include #include #include HomeAssistantLight::HomeAssistantLight(uint32_t light_id) : Light(light_id) { // Process Home Assistant specific details. General light data is loaded in the "Light" constructor. + this->_current_brightness = 0; + this->_current_color_temperature = 0; + this->_current_hue = 0; + this->_current_saturation = 0; + this->_current_mode = MQTT_MANAGER_LIGHT_MODE::DEFAULT; + this->_current_state = false; + this->_requested_brightness = 0; + this->_requested_color_temperature = 0; + this->_requested_hue = 0; + this->_requested_saturation = 0; + this->_requested_mode = MQTT_MANAGER_LIGHT_MODE::DEFAULT; + this->_requested_state = false; + if (this->_controller != MQTT_MANAGER_ENTITY_CONTROLLER::HOME_ASSISTANT) { SPDLOG_ERROR("HomeAssistantLight has not been rekognized as controlled by HOME_ASSISTANT. Will stop processing light."); return; } - auto light = database_manager::database.get(this->_id); - nlohmann::json entity_data = light.get_entity_data_json(); + nlohmann::json entity_data; + try { + auto light = database_manager::database.get(this->_id); + entity_data = light.get_entity_data_json(); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to load light {}: {}", this->_id, e.what()); + return; + } if (entity_data.contains("home_assistant_name")) { this->_home_assistant_name = entity_data["home_assistant_name"]; @@ -65,40 +84,43 @@ void HomeAssistantLight::send_state_update_to_controller() { service_data["domain"] = "light"; if (this->_requested_state) { service_data["service"] = "turn_on"; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = true; } if (this->_requested_brightness != this->_current_brightness) { service_data["service_data"]["brightness_pct"] = this->_requested_brightness; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_brightness = this->_requested_brightness; } } // This is a turn on event and it currently off. Send kelvin if turn on behavior is to use color temp. - if (this->_requested_mode == MQTT_MANAGER_LIGHT_MODE::DEFAULT || (!this->_current_state && MqttManagerConfig::get_settings().light_turn_on_behaviour == LightTurnOnBehaviour::COLOR_TEMPERATURE)) { - service_data["service_data"]["kelvin"] = this->_requested_color_temperature; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (this->_requested_mode == MQTT_MANAGER_LIGHT_MODE::DEFAULT || (!this->_current_state && MqttManagerConfig::get_light_turn_on_behaviour() == LightTurnOnBehaviour::COLOR_TEMPERATURE)) { + service_data["service_data"]["color_temp_kelvin"] = this->_requested_color_temperature <= 0 ? MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN) : this->_requested_color_temperature; + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_color_temperature = this->_requested_color_temperature; + this->_current_mode = MQTT_MANAGER_LIGHT_MODE::DEFAULT; } } if (this->_requested_mode == MQTT_MANAGER_LIGHT_MODE::DEFAULT && this->_requested_color_temperature != this->_current_color_temperature) { - service_data["service_data"]["kelvin"] = this->_requested_color_temperature; - if (MqttManagerConfig::get_settings().optimistic_mode) { + service_data["service_data"]["color_temp_kelvin"] = this->_requested_color_temperature <= 0 ? MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN) : this->_requested_color_temperature; + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_color_temperature = this->_requested_color_temperature; + this->_current_mode = MQTT_MANAGER_LIGHT_MODE::DEFAULT; } } else if (this->_requested_mode == MQTT_MANAGER_LIGHT_MODE::RGB && this->_requested_hue != this->_current_hue || this->_requested_saturation != this->_current_saturation) { service_data["service_data"]["hs_color"] = {this->_requested_hue, this->_requested_saturation}; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_hue = this->_requested_hue; this->_current_saturation = this->_requested_saturation; + this->_current_mode = MQTT_MANAGER_LIGHT_MODE::RGB; } } } else { service_data["service"] = "turn_off"; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = false; } } @@ -106,19 +128,19 @@ void HomeAssistantLight::send_state_update_to_controller() { service_data["domain"] = "switch"; if (this->_requested_state) { service_data["service"] = "turn_on"; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = true; } } else { service_data["service"] = "turn_off"; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = false; } } } HomeAssistantManager::send_json(service_data); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->send_state_update_to_nspanel(); this->_entity_changed_callbacks(this); } @@ -177,7 +199,6 @@ void HomeAssistantLight::home_assistant_event_callback(nlohmann::json data) { } this->_current_state = false; this->_requested_state = false; - this->_requested_brightness = 0; } else { SPDLOG_ERROR("Unknown entity state: {}", new_state); } @@ -229,3 +250,405 @@ void HomeAssistantLight::home_assistant_event_callback(nlohmann::json data) { } } } + +// TESTING +#if defined(TEST_MODE) && TEST_MODE == 1 +#include + +class HomeAssistantLightTest : public ::testing::Test { +protected: + HomeAssistantLightTest() { + // Initialize any necessary resources or setup for the tests + } + + void SetUp() override { + + // Initialize any necessary resources or setup for the tests + database_manager::Entity light_entity; + light_entity.entity_type = "light"; + light_entity.friendly_name = "Unit test HA light (light)"; + light_entity.room_id = 1; + light_entity.room_view_position = 2; + light_entity.set_entity_data_json({{"can_color_temperature", true}, + {"can_dim", true}, + {"can_rgb", true}, + {"controlled_by_nspanel_main_page", true}, + {"controller", "home_assistant"}, + {"home_assistant_name", "light.office_ceiling_light"}, + {"is_ceiling_light", true}, + {"openhab_control_mode", "dimmer"}, + {"openhab_item_color_temp", ""}, + {"openhab_item_dimmer", ""}, + {"openhab_item_rgb", ""}, + {"openhab_item_switch", ""}, + {"openhab_name", ""}}); + ha_light_id = database_manager::database.insert(light_entity); + // light_entity = database_manager::database.get(ha_light_id); + + SPDLOG_INFO("HA Light created in DB. Creating instance of light object."); + light = new HomeAssistantLight(ha_light_id); + } + + void TearDown() override { + // Clean up any resources or teardown after the tests + + database_manager::database.remove(ha_light_id); + // database_manager::database.remove(ha_light_switch_id); + } + + HomeAssistantLight *light = nullptr; + + int32_t ha_light_id; + int32_t ha_light_switch_id; +}; + +TEST_F(HomeAssistantLightTest, is_off_by_default) { + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); +} + +TEST_F(HomeAssistantLightTest, is_not_on_after_turn_on_in_nonoptimistic_mode) { + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "false"); + light->turn_on(false); + light->set_brightness(50, true); + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); +} + +TEST_F(HomeAssistantLightTest, is_on_after_turn_on_in_optimistic_mode) { + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + light->turn_on(false); + light->set_brightness(50, true); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); +} + +TEST_F(HomeAssistantLightTest, light_reacts_to_state_changes_from_home_assistant) { + // Enable optimistic mode for this test + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + + nlohmann::json event_data = nlohmann::json::parse(R"( + { + + "event":{ + "context":{ + "id":"01K2WNR8WT8VS6B5HTQ7ZEKSAP", + "parent_id":null, + "user_id":"d28bd745ad714c108c27c31a90406189" + }, + "data":{ + "entity_id":"light.office_ceiling_light", + "new_state":{ + "attributes":{ + "brightness":204, + "color_mode":"color_temp", + "color_temp":443, + "color_temp_kelvin":2257, + "effect":null, + "effect_list":[ + "blink", + "breathe", + "okay", + "channel_change", + "finish_effect", + "stop_effect" + ], + "friendly_name":"office_ceiling_light", + "hs_color":[ + 20, + 80 + ], + "max_color_temp_kelvin":4000, + "max_mireds":454, + "min_color_temp_kelvin":2202, + "min_mireds":250, + "rgb_color":[ + 255, + 149, + 46 + ], + "supported_color_modes":[ + "color_temp" + ], + "supported_features":44, + "xy_color":[ + 0.572, + 0.389 + ] + }, + "context":{ + "id":"01K2WNR8WT8VS6B5HTQ7ZEKSAP", + "parent_id":null, + "user_id":"d28bd745ad714c108c27c31a90406189" + }, + "entity_id":"light.office_ceiling_light", + "last_changed":"2025-08-17T18:45:35.203504+00:00", + "last_reported":"2025-08-17T18:48:00.253850+00:00", + "last_updated":"2025-08-17T18:48:00.253850+00:00", + "state":"on" + }, + "old_state": "removed_as_it_is_not_used" + }, + "event_type":"state_changed", + "origin":"LOCAL", + "time_fired":"2025-08-17T18:48:00.253850+00:00" + }, + "id":3, + "type":"event" + + } )"); + + light->home_assistant_event_callback(event_data); + + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 80); // We sent brightness 204, ie. 80% + EXPECT_EQ(light->get_color_temperature(), 2257); + EXPECT_EQ(light->get_color_temperature(), 2257); + + event_data["event"]["data"]["new_state"]["attributes"]["brightness"] = 153; + event_data["event"]["data"]["new_state"]["attributes"]["color_temp_kelvin"] = 5000; + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); // We sent brightness 153, ie. 60% + EXPECT_EQ(light->get_color_temperature(), 5000); + + event_data["event"]["data"]["new_state"]["state"] = "off"; + event_data["event"]["data"]["new_state"]["attributes"]["brightness"] = 0; + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 60); // Remember last brightness value + + // Change to RGB mode + event_data["event"]["data"]["new_state"]["state"] = "on"; + event_data["event"]["data"]["new_state"]["attributes"]["brightness"] = 153; // 60% + event_data["event"]["data"]["new_state"]["attributes"]["color_mode"] = "xy"; // TODO: Update with correct color mode as sent from HA. + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); + EXPECT_EQ(light->get_hue(), 20); + EXPECT_EQ(light->get_saturation(), 80); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that changes to RGB/HSB values as correctly interpreted + event_data["event"]["data"]["new_state"]["attributes"]["hs_color"][0] = 30; + event_data["event"]["data"]["new_state"]["attributes"]["hs_color"][1] = 60; + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); + EXPECT_EQ(light->get_hue(), 30); + EXPECT_EQ(light->get_saturation(), 60); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that we can revert back to color temp mode + event_data["event"]["data"]["new_state"]["state"] = "on"; + event_data["event"]["data"]["new_state"]["attributes"]["brightness"] = 153; // 60% + event_data["event"]["data"]["new_state"]["attributes"]["color_mode"] = "color_temp"; + event_data["event"]["data"]["new_state"]["attributes"]["color_temp_kelvin"] = 4000; + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); + EXPECT_EQ(light->get_color_temperature(), 4000); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); +} + +#define COLOR_TEMP_PERCENT_TO_KELVIN(min, max, kelvin_pct) (((max - min) / 100) * kelvin_pct + min) +TEST_F(HomeAssistantLightTest, verify_nspanel_command_compliance) { + // Enable optimistic mode for this test + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + // spdlog::set_level(spdlog::level::trace); + + NSPanelMQTTManagerCommand cmd; + cmd.set_nspanel_id(0); + NSPanelMQTTManagerCommand_LightCommand *light_cmd = cmd.mutable_light_command(); + std::vector light_ids = {light->get_id()}; + light_cmd->add_light_ids(light->get_id()); + light_cmd->set_brightness(50); + light_cmd->set_has_brightness(true); + light_cmd->set_color_temperature(50); + light_cmd->set_has_color_temperature(true); + light_cmd->set_has_hue(false); + light_cmd->set_has_saturation(false); + + light->command_callback(cmd); + + // Check that light turns on, sets correct brightness and color temperature. + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 50)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + // Verify that only color temperature can be change. + light_cmd->set_has_brightness(false); + light_cmd->set_color_temperature(70); + light_cmd->set_has_color_temperature(true); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + // Verify that only brightness can be change. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(true); + light_cmd->set_brightness(70); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + // Verify that hue and saturation can be changed and the lights goes into RGB mode. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(false); + light_cmd->set_hue(30); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(60); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 30); + EXPECT_EQ(light->get_saturation(), 60); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that only hue can be changed. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(false); + light_cmd->set_hue(40); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(60); + light_cmd->set_has_saturation(false); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 40); + EXPECT_EQ(light->get_saturation(), 60); // Verify that value is unchanged. + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that only saturation can be changed. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(false); + light_cmd->set_hue(40); + light_cmd->set_has_hue(false); + light_cmd->set_saturation(70); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 40); // Verify that value is unchanged. + EXPECT_EQ(light->get_saturation(), 70); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that brightness can be changed and RGB is kept. + light_cmd->set_has_color_temperature(false); + light_cmd->set_brightness(100); + light_cmd->set_has_brightness(true); + light_cmd->set_hue(40); + light_cmd->set_has_hue(false); + light_cmd->set_saturation(70); + light_cmd->set_has_saturation(false); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 100); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 40); // Verify that value is unchanged. + EXPECT_EQ(light->get_saturation(), 70); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); +} + +TEST_F(HomeAssistantLightTest, verify_optimistic_mode_compliance) { + // Enable optimistic mode for this test + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + // spdlog::set_level(spdlog::level::trace); + + NSPanelMQTTManagerCommand cmd; + cmd.set_nspanel_id(0); + NSPanelMQTTManagerCommand_LightCommand *light_cmd = cmd.mutable_light_command(); + std::vector light_ids = {light->get_id()}; + light_cmd->add_light_ids(light->get_id()); + light_cmd->set_brightness(50); + light_cmd->set_has_brightness(true); + light_cmd->set_color_temperature(50); + light_cmd->set_has_color_temperature(true); + light_cmd->set_has_hue(false); + light_cmd->set_has_saturation(false); + + light->command_callback(cmd); + + // Check that light turns on, sets correct brightness and color temperature. + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 50)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + light_cmd->set_has_brightness(false); + light_cmd->set_has_color_temperature(false); + light_cmd->set_hue(30); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(50); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + + EXPECT_EQ(light->get_state(), true); // Verify light is still on + EXPECT_EQ(light->get_brightness(), 50); // Verify light is still set to 50% brightness + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 50)); // Verify color temp has not changed. + EXPECT_EQ(light->get_hue(), 30); + EXPECT_EQ(light->get_saturation(), 50); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); // Light should have switched to RGB mode. +} + +TEST_F(HomeAssistantLightTest, verify_non_optimistic_mode_compliance) { + // Turn off optimistic mode and verify that no values change. + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "false"); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + // spdlog::set_level(spdlog::level::trace); + + NSPanelMQTTManagerCommand cmd; + cmd.set_nspanel_id(0); + NSPanelMQTTManagerCommand_LightCommand *light_cmd = cmd.mutable_light_command(); + std::vector light_ids = {light->get_id()}; + light_cmd->add_light_ids(light->get_id()); + light_cmd->set_brightness(50); + light_cmd->set_has_brightness(true); + light_cmd->set_color_temperature(50); + light_cmd->set_has_color_temperature(true); + light_cmd->set_has_hue(false); + light_cmd->set_has_saturation(false); + light->command_callback(cmd); + + // Verify light is still off, brightness is still 0 and hue and saturation did not change. + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); + EXPECT_EQ(light->get_color_temperature(), 0); + EXPECT_EQ(light->get_hue(), 0); + EXPECT_EQ(light->get_saturation(), 0); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); // Light should have switched to RGB mode. + + light_cmd->set_has_brightness(false); + light_cmd->set_has_color_temperature(false); + light_cmd->set_hue(30); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(30); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + + // Verify light is still off, brightness is still 0 and hue and saturation did not change. + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); + EXPECT_EQ(light->get_color_temperature(), 0); + EXPECT_EQ(light->get_hue(), 0); + EXPECT_EQ(light->get_saturation(), 0); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); // Light should have switched to RGB mode. +} + +#endif // !MQTT_MANAGER_HOME_ASSISTANT_LIGHT_TEST diff --git a/docker/MQTTManager/include/light/home_assistant_light.hpp b/docker/MQTTManager/include/light/home_assistant_light.hpp index 641d2418..1891cc57 100644 --- a/docker/MQTTManager/include/light/home_assistant_light.hpp +++ b/docker/MQTTManager/include/light/home_assistant_light.hpp @@ -2,7 +2,6 @@ #define MQTT_MANAGER_HOME_ASSISTANT_LIGHT #include "light.hpp" -#include "protobuf_general.pb.h" enum MQTT_MANAGER_HOME_ASSISTANT_LIGHT_TYPE { TYPE_LIGHT, @@ -20,5 +19,4 @@ class HomeAssistantLight : public Light { std::string _home_assistant_name; MQTT_MANAGER_HOME_ASSISTANT_LIGHT_TYPE _home_assistant_light_type; }; - #endif // !MQTT_MANAGER_HOME_ASSISTANT_LIGHT diff --git a/docker/MQTTManager/include/light/light.cpp b/docker/MQTTManager/include/light/light.cpp index 07232e23..156a081f 100644 --- a/docker/MQTTManager/include/light/light.cpp +++ b/docker/MQTTManager/include/light/light.cpp @@ -49,7 +49,19 @@ uint16_t Light::get_room_id() { } void Light::reload_config() { - auto light = database_manager::database.get(this->_id); + database_manager::Entity light; + try { + light = database_manager::database.get(this->_id); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to load light {}: {}", this->_id, e.what()); + SPDLOG_ERROR("Will use default values."); + this->_name = "UNKNOWN"; + this->_room_id = 0; + this->_entity_page_id = 0; + this->_entity_page_slot = 0; + return; + } + this->_name = light.friendly_name; SPDLOG_DEBUG("Loading light {}::{}.", this->_id, this->_name); @@ -158,8 +170,8 @@ void Light::send_state_update_to_nspanel() { SPDLOG_ERROR("Unknown light mode! Will assume default mode."); } - float kelvin_pct = (((float)this->_current_color_temperature - MqttManagerConfig::get_settings().color_temp_min) / (MqttManagerConfig::get_settings().color_temp_max - MqttManagerConfig::get_settings().color_temp_min)) * 100; - if (MqttManagerConfig::get_settings().reverse_color_temperature_slider) { + float kelvin_pct = (((float)this->_current_color_temperature - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN)) / (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX) - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN))) * 100; + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::REVERSE_COLOR_TEMP)) { kelvin_pct = 100 - kelvin_pct; } light_state->set_color_temp(kelvin_pct); @@ -181,7 +193,7 @@ void Light::turn_on(bool send_update) { if (send_update) { this->send_state_update_to_controller(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = true; } } @@ -193,7 +205,7 @@ void Light::turn_off(bool send_update) { if (send_update) { this->send_state_update_to_controller(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = false; } } @@ -214,7 +226,7 @@ void Light::set_brightness(uint8_t brightness, bool send_update) { if (send_update) { this->send_state_update_to_controller(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_brightness = brightness; } } @@ -231,7 +243,7 @@ void Light::set_color_temperature(uint color_temperature, bool send_update) { if (send_update) { this->send_state_update_to_controller(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_color_temperature = color_temperature; } } @@ -248,7 +260,7 @@ void Light::set_hue(uint16_t hue, bool send_update) { if (send_update) { this->send_state_update_to_controller(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_hue = hue; } } @@ -265,7 +277,7 @@ void Light::set_saturation(uint8_t saturation, bool send_update) { if (send_update) { this->send_state_update_to_controller(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_saturation = saturation; } } @@ -284,7 +296,7 @@ void Light::set_hsb(uint16_t hue, uint8_t saturation, uint8_t brightness, bool s if (send_update) { this->send_state_update_to_controller(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_hue = hue; this->_current_saturation = saturation; this->_current_brightness = brightness; @@ -335,6 +347,7 @@ void Light::command_callback(NSPanelMQTTManagerCommand &command) { }); if (light_id != command.light_command().light_ids().end()) { + SPDLOG_TRACE("Light {}::{} handling light command from NSPanel with ID {}.", this->_id, this->_name, command.nspanel_id()); NSPanelMQTTManagerCommand_LightCommand cmd = command.light_command(); if (cmd.has_brightness()) { this->set_brightness(cmd.brightness(), false); @@ -346,11 +359,11 @@ void Light::command_callback(NSPanelMQTTManagerCommand &command) { } if (cmd.has_color_temperature()) { // Convert color temperature (0-100) to actual color temperature in kelvin. - uint32_t color_temperature_kelvin = cmd.color_temperature() * ((MqttManagerConfig::get_settings().color_temp_max - MqttManagerConfig::get_settings().color_temp_min) / 100); - if (MqttManagerConfig::get_settings().reverse_color_temperature_slider) { - color_temperature_kelvin = MqttManagerConfig::get_settings().color_temp_max - color_temperature_kelvin; + uint32_t color_temperature_kelvin = cmd.color_temperature() * ((MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX) - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN)) / 100); + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::REVERSE_COLOR_TEMP)) { + color_temperature_kelvin = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX) - color_temperature_kelvin; } else { - color_temperature_kelvin += MqttManagerConfig::get_settings().color_temp_min; + color_temperature_kelvin += MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); } this->set_color_temperature(color_temperature_kelvin, false); } @@ -375,7 +388,7 @@ void Light::toggle() { } else { // Apply default "turn on brightness" if it is requested to become 0 as that won't work. if (this->_requested_brightness == 0) { - this->_requested_brightness = std::stoi(MqttManagerConfig::get_setting_with_default("light_turn_on_brightness", "50")); + this->_requested_brightness = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::LIGHT_TURN_ON_BRIGHTNESS); } this->turn_on(true); } @@ -406,6 +419,6 @@ uint16_t Light::get_icon_active_color() { } std::string Light::get_mqtt_state_topic() { - std::string manager_address = MqttManagerConfig::get_settings().manager_address; + std::string manager_address = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS); return fmt::format("nspanel/mqttmanager_{}/entities/lights/{}/state", manager_address, this->get_id()); } diff --git a/docker/MQTTManager/include/light/light.hpp b/docker/MQTTManager/include/light/light.hpp index b4971c07..d00a832f 100644 --- a/docker/MQTTManager/include/light/light.hpp +++ b/docker/MQTTManager/include/light/light.hpp @@ -216,18 +216,18 @@ class Light : public MqttManagerEntity { bool _can_rgb; bool _current_state; - uint8_t _current_brightness; - uint16_t _current_color_temperature; - uint16_t _current_hue; - uint8_t _current_saturation; - MQTT_MANAGER_LIGHT_MODE _current_mode; - MQTT_MANAGER_LIGHT_TYPE _light_type; - - bool _requested_state; - uint8_t _requested_brightness; - uint16_t _requested_color_temperature; - uint16_t _requested_hue; - uint8_t _requested_saturation; + uint8_t _current_brightness = 0; + uint16_t _current_color_temperature = 0; + uint16_t _current_hue = 0; + uint8_t _current_saturation = 0; + MQTT_MANAGER_LIGHT_MODE _current_mode = MQTT_MANAGER_LIGHT_MODE::DEFAULT; + MQTT_MANAGER_LIGHT_TYPE _light_type = MQTT_MANAGER_LIGHT_TYPE::CEILING; + + bool _requested_state = false; + uint8_t _requested_brightness = 0; + uint16_t _requested_color_temperature = 0; + uint16_t _requested_hue = 0; + uint8_t _requested_saturation = 0; NSPanelEntityState _last_entity_state; MQTT_MANAGER_LIGHT_MODE _requested_mode; diff --git a/docker/MQTTManager/include/light/openhab_light.cpp b/docker/MQTTManager/include/light/openhab_light.cpp index 8dfc4b54..195854e3 100644 --- a/docker/MQTTManager/include/light/openhab_light.cpp +++ b/docker/MQTTManager/include/light/openhab_light.cpp @@ -16,23 +16,37 @@ #include #include #include +#include #include #include +#include #include #include +#include -uint64_t CurrentTimeMilliseconds() { +inline uint64_t CurrentTimeMilliseconds() { return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); } OpenhabLight::OpenhabLight(uint32_t light_id) : Light(light_id) { // Process OpenHAB specific details. General light data is loaded in the "Light" constructor. - // this->_last_light_mode_change = 0; this->_last_rgb_change = 0; this->_last_on_off_change = 0; this->_last_brightness_change = 0; this->_last_color_temp_change = 0; + this->_current_mode = MQTT_MANAGER_LIGHT_MODE::DEFAULT; + this->_current_brightness = 0; + this->_current_color_temperature = 0; + this->_current_hue = 0; + this->_current_saturation = 0; + this->_current_state = false; + this->_requested_mode = MQTT_MANAGER_LIGHT_MODE::DEFAULT; + this->_requested_brightness = 0; + this->_requested_color_temperature = 0; + this->_requested_hue = 0; + this->_requested_saturation = 0; + this->_requested_state = false; if (this->_controller != MQTT_MANAGER_ENTITY_CONTROLLER::OPENHAB) { SPDLOG_ERROR("OpenhabLight has not been recognized as controlled by OPENHAB. Will stop processing light."); @@ -135,7 +149,7 @@ void OpenhabLight::send_state_update_to_controller() { payload_data["type"] = "OnOff"; payload_data["value"] = this->_requested_state ? "ON" : "OFF"; service_data["payload"] = payload_data.dump(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = this->_requested_state; this->_last_on_off_change = CurrentTimeMilliseconds(); this->_entity_changed_callbacks(this); @@ -146,7 +160,7 @@ void OpenhabLight::send_state_update_to_controller() { // If the light is off but in RGB mode and the user has configured the lights to turn on in "color temp" mode, force it back to color temp mode. bool force_send_kelvin = false; - if (this->_requested_state && !this->_current_state && MqttManagerConfig::get_settings().light_turn_on_behaviour == LightTurnOnBehaviour::COLOR_TEMPERATURE && this->_can_color_temperature && (this->_requested_mode != this->_current_mode && this->_requested_mode == MQTT_MANAGER_LIGHT_MODE::DEFAULT)) { + if (this->_requested_state && !this->_current_state && MqttManagerConfig::get_light_turn_on_behaviour() == LightTurnOnBehaviour::COLOR_TEMPERATURE && this->_can_color_temperature && (this->_requested_mode != this->_current_mode && this->_requested_mode == MQTT_MANAGER_LIGHT_MODE::DEFAULT)) { this->_requested_mode = MQTT_MANAGER_LIGHT_MODE::DEFAULT; force_send_kelvin = true; } @@ -156,7 +170,7 @@ void OpenhabLight::send_state_update_to_controller() { SPDLOG_DEBUG("Setting light {}::{} to level: 0", this->_id, this->_name); payload_data["value"] = 0; service_data["payload"] = payload_data.dump(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = false; this->_last_on_off_change = CurrentTimeMilliseconds(); } @@ -169,7 +183,7 @@ void OpenhabLight::send_state_update_to_controller() { SPDLOG_DEBUG("Setting light {}::{} to level: {}", this->_id, this->_name, this->_requested_brightness); payload_data["value"] = this->_requested_brightness; service_data["payload"] = payload_data.dump(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_last_on_off_change = CurrentTimeMilliseconds(); this->_last_brightness_change = CurrentTimeMilliseconds(); this->_current_state = true; @@ -180,8 +194,8 @@ void OpenhabLight::send_state_update_to_controller() { if ((this->_can_color_temperature && this->_requested_color_temperature != this->_current_color_temperature) || force_send_kelvin) { // Calculate color temp percentage - uint16_t kelvin_max_floored = MqttManagerConfig::get_settings().color_temp_max - MqttManagerConfig::get_settings().color_temp_min; - uint16_t kelvin_floored = this->_requested_color_temperature - MqttManagerConfig::get_settings().color_temp_min; + uint16_t kelvin_max_floored = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX) - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint16_t kelvin_floored = this->_requested_color_temperature - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); uint8_t color_temp_percentage = 100 - int(((float)kelvin_floored / (float)kelvin_max_floored) * 100); if (color_temp_percentage > 100) { color_temp_percentage = 100; @@ -193,8 +207,8 @@ void OpenhabLight::send_state_update_to_controller() { service_data["topic"] = fmt::format("openhab/items/{}/command", this->_openhab_item_color_temperature); payload_data["value"] = color_temp_percentage; service_data["payload"] = payload_data.dump(); - this->_current_mode = MQTT_MANAGER_LIGHT_MODE::DEFAULT; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { + this->_current_mode = MQTT_MANAGER_LIGHT_MODE::DEFAULT; this->_last_color_temp_change = CurrentTimeMilliseconds(); this->_last_light_mode_change = CurrentTimeMilliseconds(); // Make sure we do not go to "RGB" mode when Zigbee2Mqtt sends updated HSB values to reflect color temp value this->_current_color_temperature = this->_requested_color_temperature; @@ -207,8 +221,8 @@ void OpenhabLight::send_state_update_to_controller() { payload_data["type"] = "HSB"; payload_data["value"] = fmt::format("{},{},{}", this->_requested_hue, this->_requested_saturation, this->_requested_brightness); service_data["payload"] = payload_data.dump(); - this->_current_mode = MQTT_MANAGER_LIGHT_MODE::RGB; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { + this->_current_mode = MQTT_MANAGER_LIGHT_MODE::RGB; this->_last_rgb_change = CurrentTimeMilliseconds(); this->_last_light_mode_change = CurrentTimeMilliseconds(); // Make sure we do not go to "color temp" mode when Zigbee2Mqtt sends updated color temp value to reflect HSB value this->_current_hue = this->_requested_hue; @@ -218,7 +232,7 @@ void OpenhabLight::send_state_update_to_controller() { OpenhabManager::send_json(service_data); } - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->send_state_update_to_nspanel(); this->_entity_changed_callbacks(this); } @@ -346,8 +360,8 @@ void OpenhabLight::openhab_event_callback(nlohmann::json data) { color_temperature = 100; } // Convert from percentage to actual color temp. - unsigned long kelvin_max_floored = MqttManagerConfig::get_settings().color_temp_max - MqttManagerConfig::get_settings().color_temp_min; - uint16_t kelvin = std::round(MqttManagerConfig::get_settings().color_temp_min + int((color_temperature / (double)100) * kelvin_max_floored)); + unsigned long kelvin_max_floored = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX) - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint16_t kelvin = std::round(MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN) + int((color_temperature / (double)100) * kelvin_max_floored)); if (kelvin != this->_current_color_temperature) { this->_current_color_temperature = kelvin; @@ -459,8 +473,8 @@ void OpenhabLight::openhab_event_callback(nlohmann::json data) { color_temperature = 100; } // Convert from percentage to actual color temp. - unsigned long kelvin_max_floored = MqttManagerConfig::get_settings().color_temp_max - MqttManagerConfig::get_settings().color_temp_min; - uint16_t kelvin = MqttManagerConfig::get_settings().color_temp_min + int((color_temperature / (double)100) * kelvin_max_floored); + unsigned long kelvin_max_floored = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX) - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint16_t kelvin = std::round(MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN) + int((color_temperature / (double)100) * kelvin_max_floored)); this->_current_color_temperature = kelvin; this->_requested_color_temperature = this->_current_color_temperature; @@ -554,3 +568,393 @@ void OpenhabLight::_openhab_group_rgb_item_state_changed_event_thread_func() { this->openhab_event_callback(this->_last_group_rgb_item_state_changed_event_data); this->_openhab_group_rgb_item_state_changed_event_thread_running = false; } + +// TESTING +#if defined(TEST_MODE) && TEST_MODE == 1 +#include +#define COLOR_TEMP_PERCENT_TO_KELVIN(min, max, kelvin_pct) (((max - min) / 100) * kelvin_pct + min) + +class OpenhabLightTest : public ::testing::Test { +protected: + OpenhabLightTest() { + // Initialize any necessary resources or setup for the tests + } + + void SetUp() override { + + // Initialize any necessary resources or setup for the tests + database_manager::Entity light_entity; + light_entity.entity_type = "light"; + light_entity.friendly_name = "Unit test OH light (light)"; + light_entity.room_id = 1; + light_entity.room_view_position = 2; + light_entity.set_entity_data_json({{"can_color_temperature", true}, + {"can_dim", true}, + {"can_rgb", true}, + {"controlled_by_nspanel_main_page", true}, + {"controller", "openhab"}, + {"home_assistant_name", ""}, + {"is_ceiling_light", true}, + {"openhab_control_mode", "dimmer"}, + {"openhab_item_color_temp", "oh_item_color_temp"}, + {"openhab_item_dimmer", "oh_item_dimmer"}, + {"openhab_item_rgb", "oh_item_hsb"}, + {"openhab_item_switch", ""}, + {"openhab_name", "oh_name"}}); + oh_light_id = database_manager::database.insert(light_entity); + // light_entity = database_manager::database.get(ha_light_id); + + SPDLOG_INFO("OpenHAB Light created in DB. Creating instance of light object."); + light = new OpenhabLight(oh_light_id); + } + + void TearDown() override { + // Clean up any resources or teardown after the tests + + database_manager::database.remove(oh_light_id); + // database_manager::database.remove(ha_light_switch_id); + } + + OpenhabLight *light = nullptr; + + int32_t oh_light_id; + int32_t oh_light_switch_id; +}; + +TEST_F(OpenhabLightTest, is_off_by_default) { + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); +} + +TEST_F(OpenhabLightTest, is_not_on_after_turn_on_in_nonoptimistic_mode) { + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "false"); + light->turn_on(false); + light->set_brightness(50, true); + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); +} + +TEST_F(OpenhabLightTest, is_on_after_turn_on_in_optimistic_mode) { + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + light->turn_on(false); + light->set_brightness(50, true); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); +} + +TEST_F(OpenhabLightTest, light_reacts_to_state_changes_from_openhab) { + // Enable optimistic mode for this test + spdlog::set_level(spdlog::level::debug); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + + // Verify brightness changes are handled correctly + light->openhab_event_callback( + nlohmann::json::parse(R"( + {"payload":"{\"type\":\"Percent\",\"value\":\"80\"}","topic":"openhab/items/oh_item_dimmer/stateupdated","type":"ItemStateChangedEvent"} + )")); + + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 80); + std::this_thread::sleep_for(std::chrono::milliseconds(1050)); // Sleep for 1 second as to not skip the next state update. + + light->openhab_event_callback( + nlohmann::json::parse(R"( + {"payload":"{\"type\":\"Percent\",\"value\":\"60\"}","topic":"openhab/items/oh_item_dimmer/stateupdated","type":"ItemStateChangedEvent"} + )")); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); + std::this_thread::sleep_for(std::chrono::milliseconds(1050)); // Sleep for 1 second as to not skip the next state update. + + light->openhab_event_callback( + nlohmann::json::parse(R"( + {"payload":"{\"type\":\"Percent\",\"value\":\"0\"}","topic":"openhab/items/oh_item_dimmer/stateupdated","type":"ItemStateChangedEvent"} + )")); + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 60); // Light should remember last state. + std::this_thread::sleep_for(std::chrono::milliseconds(1050)); // Sleep for 1 second as to not skip the next state update. + + // Turn light back on for the rest of the tests + light->openhab_event_callback( + nlohmann::json::parse(R"( + {"payload":"{\"type\":\"Percent\",\"value\":\"100\"}","topic":"openhab/items/oh_item_dimmer/stateupdated","type":"ItemStateChangedEvent"} + )")); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 100); + std::this_thread::sleep_for(std::chrono::milliseconds(1050)); // Sleep for 1 second as to not skip the next state update. + + // Test color temperature + light->openhab_event_callback( + nlohmann::json::parse(R"( + {"payload":"{\"type\":\"Percent\",\"value\":\"80\"}","topic":"openhab/items/oh_item_color_temp/stateupdated","type":"ItemStateChangedEvent"} + )")); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, (100 - 80))); // Color temp percentage to kelvin conversion is reversed for OpenHAB + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + std::this_thread::sleep_for(std::chrono::milliseconds(1050)); // Sleep for 1 second as to not skip the next state update. + + light->openhab_event_callback( + nlohmann::json::parse(R"( + {"payload":"{\"type\":\"Percent\",\"value\":\"0\"}","topic":"openhab/items/oh_item_color_temp/stateupdated","type":"ItemStateChangedEvent"} + )")); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, (100 - 0))); // Color temp percentage to kelvin conversion is reversed for OpenHAB + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + std::this_thread::sleep_for(std::chrono::milliseconds(1050)); // Sleep for 1 second as to not skip the next state update. + + light->openhab_event_callback( + nlohmann::json::parse(R"( + {"payload":"{\"type\":\"Percent\",\"value\":\"50\"}","topic":"openhab/items/oh_item_color_temp/stateupdated","type":"ItemStateChangedEvent"} + )")); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, (100 - 50))); // Color temp percentage to kelvin conversion is reversed for OpenHAB + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + std::this_thread::sleep_for(std::chrono::milliseconds(1050)); // Sleep for 1 second as to not skip the next state update. + + // Test RGB + light->openhab_event_callback( + nlohmann::json::parse(R"( + {"payload":"{\"type\":\"Percent\",\"value\":\"215,47,11\"}","topic":"openhab/items/oh_item_hsb/stateupdated","type":"ItemStateChangedEvent"} + )")); + EXPECT_EQ(light->get_hue(), 215); + EXPECT_EQ(light->get_saturation(), 47); + EXPECT_EQ(light->get_brightness(), 11); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + std::this_thread::sleep_for(std::chrono::milliseconds(1050)); // Sleep for 1 second as to not skip the next state update. + + light->openhab_event_callback( + nlohmann::json::parse(R"( + {"payload":"{\"type\":\"Percent\",\"value\":\"200,47,11\"}","topic":"openhab/items/oh_item_hsb/stateupdated","type":"ItemStateChangedEvent"} + )")); + EXPECT_EQ(light->get_hue(), 200); + EXPECT_EQ(light->get_saturation(), 47); + EXPECT_EQ(light->get_brightness(), 11); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + std::this_thread::sleep_for(std::chrono::milliseconds(1050)); // Sleep for 1 second as to not skip the next state update. + + light->openhab_event_callback( + nlohmann::json::parse(R"( + {"payload":"{\"type\":\"Percent\",\"value\":\"200,30,11\"}","topic":"openhab/items/oh_item_hsb/stateupdated","type":"ItemStateChangedEvent"} + )")); + EXPECT_EQ(light->get_hue(), 200); + EXPECT_EQ(light->get_saturation(), 30); + EXPECT_EQ(light->get_brightness(), 11); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + std::this_thread::sleep_for(std::chrono::milliseconds(1050)); // Sleep for 1 second as to not skip the next state update. + + light->openhab_event_callback( + nlohmann::json::parse(R"( + {"payload":"{\"type\":\"Percent\",\"value\":\"200,30,100\"}","topic":"openhab/items/oh_item_hsb/stateupdated","type":"ItemStateChangedEvent"} + )")); + EXPECT_EQ(light->get_hue(), 200); + EXPECT_EQ(light->get_saturation(), 30); + EXPECT_EQ(light->get_brightness(), 100); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + std::this_thread::sleep_for(std::chrono::milliseconds(1050)); // Sleep for 1 second as to not skip the next state update. + + // Verify light goes back to color temp mode when a new color temp was received. + light->openhab_event_callback( + nlohmann::json::parse(R"( + {"payload":"{\"type\":\"Percent\",\"value\":\"80\"}","topic":"openhab/items/oh_item_color_temp/stateupdated","type":"ItemStateChangedEvent"} + )")); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, (100 - 80))); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); +} + +TEST_F(OpenhabLightTest, verify_nspanel_command_compliance) { + // Enable optimistic mode for this test + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + // spdlog::set_level(spdlog::level::trace); + + NSPanelMQTTManagerCommand cmd; + cmd.set_nspanel_id(0); + NSPanelMQTTManagerCommand_LightCommand *light_cmd = cmd.mutable_light_command(); + std::vector light_ids = {light->get_id()}; + light_cmd->add_light_ids(light->get_id()); + light_cmd->set_brightness(50); + light_cmd->set_has_brightness(true); + light_cmd->set_color_temperature(50); + light_cmd->set_has_color_temperature(true); + light_cmd->set_has_hue(false); + light_cmd->set_has_saturation(false); + + light->command_callback(cmd); + + // Check that light turns on, sets correct brightness and color temperature. + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 50)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + // Verify that only color temperature can be change. + light_cmd->set_has_brightness(false); + light_cmd->set_color_temperature(70); + light_cmd->set_has_color_temperature(true); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + // Verify that only brightness can be change. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(true); + light_cmd->set_brightness(70); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + // Verify that hue and saturation can be changed and the lights goes into RGB mode. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(false); + light_cmd->set_hue(30); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(60); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 30); + EXPECT_EQ(light->get_saturation(), 60); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that only hue can be changed. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(false); + light_cmd->set_hue(40); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(60); + light_cmd->set_has_saturation(false); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 40); + EXPECT_EQ(light->get_saturation(), 60); // Verify that value is unchanged. + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that only saturation can be changed. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(false); + light_cmd->set_hue(40); + light_cmd->set_has_hue(false); + light_cmd->set_saturation(70); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 40); // Verify that value is unchanged. + EXPECT_EQ(light->get_saturation(), 70); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that brightness can be changed and RGB is kept. + light_cmd->set_has_color_temperature(false); + light_cmd->set_brightness(100); + light_cmd->set_has_brightness(true); + light_cmd->set_hue(40); + light_cmd->set_has_hue(false); + light_cmd->set_saturation(70); + light_cmd->set_has_saturation(false); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 100); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 40); // Verify that value is unchanged. + EXPECT_EQ(light->get_saturation(), 70); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); +} + +TEST_F(OpenhabLightTest, verify_optimistic_mode_compliance) { + // Enable optimistic mode for this test + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + // spdlog::set_level(spdlog::level::trace); + + NSPanelMQTTManagerCommand cmd; + cmd.set_nspanel_id(0); + NSPanelMQTTManagerCommand_LightCommand *light_cmd = cmd.mutable_light_command(); + std::vector light_ids = {light->get_id()}; + light_cmd->add_light_ids(light->get_id()); + light_cmd->set_brightness(50); + light_cmd->set_has_brightness(true); + light_cmd->set_color_temperature(50); + light_cmd->set_has_color_temperature(true); + light_cmd->set_has_hue(false); + light_cmd->set_has_saturation(false); + + light->command_callback(cmd); + + // Check that light turns on, sets correct brightness and color temperature. + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 50)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + light_cmd->set_has_brightness(false); + light_cmd->set_has_color_temperature(false); + light_cmd->set_hue(30); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(50); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + + EXPECT_EQ(light->get_state(), true); // Verify light is still on + EXPECT_EQ(light->get_brightness(), 50); // Verify light is still set to 50% brightness + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 50)); // Verify color temp has not changed. + EXPECT_EQ(light->get_hue(), 30); + EXPECT_EQ(light->get_saturation(), 50); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); // Light should have switched to RGB mode. +} + +TEST_F(OpenhabLightTest, verify_non_optimistic_mode_compliance) { + // Turn off optimistic mode and verify that no values change. + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "false"); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + // spdlog::set_level(spdlog::level::trace); + + NSPanelMQTTManagerCommand cmd; + cmd.set_nspanel_id(0); + NSPanelMQTTManagerCommand_LightCommand *light_cmd = cmd.mutable_light_command(); + std::vector light_ids = {light->get_id()}; + light_cmd->add_light_ids(light->get_id()); + light_cmd->set_brightness(50); + light_cmd->set_has_brightness(true); + light_cmd->set_color_temperature(50); + light_cmd->set_has_color_temperature(true); + light_cmd->set_has_hue(false); + light_cmd->set_has_saturation(false); + light->command_callback(cmd); + + // Verify light is still off, brightness is still 0 and hue and saturation did not change. + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); + EXPECT_EQ(light->get_color_temperature(), 0); + EXPECT_EQ(light->get_hue(), 0); + EXPECT_EQ(light->get_saturation(), 0); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); // Light should have switched to RGB mode. + + light_cmd->set_has_brightness(false); + light_cmd->set_has_color_temperature(false); + light_cmd->set_hue(30); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(30); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + + // Verify light is still off, brightness is still 0 and hue and saturation did not change. + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); + EXPECT_EQ(light->get_color_temperature(), 0); + EXPECT_EQ(light->get_hue(), 0); + EXPECT_EQ(light->get_saturation(), 0); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); // Light should have switched to RGB mode. +} + +#endif // !MQTT_MANAGER_HOME_ASSISTANT_LIGHT_TEST diff --git a/docker/MQTTManager/include/mqtt_manager/mqtt_manager.cpp b/docker/MQTTManager/include/mqtt_manager/mqtt_manager.cpp index 4e343af7..299aa5c4 100644 --- a/docker/MQTTManager/include/mqtt_manager/mqtt_manager.cpp +++ b/docker/MQTTManager/include/mqtt_manager/mqtt_manager.cpp @@ -1,9 +1,9 @@ #include "mqtt_manager.hpp" #include +#include +#include #include #include -#include -#include #include #include #include @@ -34,10 +34,11 @@ inline bool file_exists(const char *name) { void MQTT_Manager::init() { MQTT_Manager::reload_config(); // This will also start a new thread to handle MQTT messages. WebsocketServer::register_warning(WebsocketServer::ActiveWarningLevel::ERROR, "MQTT not connected."); + + WebsocketServer::attach_stomp_global_callback(MQTT_Manager::_process_stomp_message); } void MQTT_Manager::connect() { - if (!MQTT_Manager::_process_messages_thread.joinable()) { SPDLOG_INFO("Starting MQTT message processing thread."); MQTT_Manager::_process_messages_thread = std::thread(MQTT_Manager::_process_mqtt_messages); @@ -89,10 +90,10 @@ void MQTT_Manager::reload_config() { std::string password = ""; { std::lock_guard lock_guard(MQTT_Manager::_settings_mutex); - address = MqttManagerConfig::get_setting_with_default("mqtt_server", ""); - port = std::stoi(MqttManagerConfig::get_setting_with_default("mqtt_port", "1883")); - username = MqttManagerConfig::get_setting_with_default("mqtt_username", ""); - password = MqttManagerConfig::get_setting_with_default("mqtt_password", ""); + address = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MQTT_SERVER); + port = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MQTT_PORT); + username = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MQTT_USERNAME); + password = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MQTT_PASSWORD); } if (MQTT_Manager::_mqtt_address.compare(address) != 0 || @@ -185,12 +186,16 @@ void MQTT_Manager::_reconnect_mqtt_client() { MQTT_Manager::_mqtt_client = new mqtt::client(connection_url, mqtt_client_name.c_str()); + auto last_will = mqtt::message(fmt::format("nspanel/mqttmanager_{}/status/status", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)), + "offline", 1, true); + auto connOpts = mqtt::connect_options_builder() .user_name(MQTT_Manager::_mqtt_username) .password(MQTT_Manager::_mqtt_password) .keep_alive_interval(std::chrono::seconds(30)) .automatic_reconnect(std::chrono::seconds(2), std::chrono::seconds(10)) .clean_session(false) + .will(std::move(last_will)) .finalize(); MQTT_Manager::_stop_consuming = false; @@ -207,7 +212,15 @@ void MQTT_Manager::_reconnect_mqtt_client() { std::this_thread::sleep_for(std::chrono::milliseconds(250)); } WebsocketServer::remove_warning("MQTT not connected."); - SPDLOG_INFO("Established connection to MQTT server."); + SPDLOG_INFO("Established connection to MQTT server. Sending updated MQTT status."); + + MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/status", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)), + "online", true); + + // Loop over retained MQTT messages and send them again to the MQTT broker as it may have restarted and lost retained messages. + for (auto it = MQTT_Manager::_mqtt_retain_buffer.cbegin(); it != MQTT_Manager::_mqtt_retain_buffer.cend(); it++) { + MQTT_Manager::publish(it->first, it->second, true); + } try { SPDLOG_DEBUG("Subscribing to registered MQTT topics."); @@ -279,12 +292,29 @@ void MQTT_Manager::publish(const std::string &topic, const std::string &payload, return; // It's not allowed to publish to empty topic. } + if (retain) { + MQTT_Manager::_mqtt_retain_buffer[topic] = payload; + } + std::lock_guard mutex_guard(MQTT_Manager::_mqtt_client_mutex); mqtt::message_ptr msg = mqtt::make_message(topic.c_str(), payload.c_str(), payload.size(), 0, retain); if (MQTT_Manager::_mqtt_client != nullptr) { if (MQTT_Manager::is_connected()) { SPDLOG_TRACE("Publising '{}' -> '{}'", topic, payload); MQTT_Manager::_mqtt_client->publish(msg); + + // Replicate messages into the websocket STOMP topics. + // Convert payload to base64 as to be able to send it over the websocket. + std::size_t encoded_size = (payload.size() + 2) / 3 * 4; + std::vector encoded_buffer(encoded_size); + // Encode the string + std::size_t written = boost::beast::detail::base64::encode( + encoded_buffer.data(), + payload.data(), + payload.size()); + encoded_buffer.resize(written); + WebsocketServer::set_stomp_topic_retained(fmt::format("mqtt/{}", topic), retain); + WebsocketServer::update_stomp_topic_value(fmt::format("mqtt/{}", topic), std::string(encoded_buffer.begin(), encoded_buffer.end())); } else { MQTT_Manager::_mqtt_messages_buffer.push_back(msg); } @@ -296,7 +326,7 @@ void MQTT_Manager::publish(const std::string &topic, const std::string &payload, void MQTT_Manager::publish_protobuf(const std::string &topic, google::protobuf::Message &payload, bool retain) { std::string payload_string; if (payload.SerializeToString(&payload_string)) { - MQTT_Manager::publish(topic, payload_string.c_str(), retain); + MQTT_Manager::publish(topic, payload_string, retain); } else { SPDLOG_ERROR("Tried to send message to topic '{}' but serialization of protobuf object failed.", topic); } @@ -308,7 +338,10 @@ void MQTT_Manager::clear_retain(const std::string &topic) { return; } + MQTT_Manager::_mqtt_retain_buffer.erase(topic); + std::lock_guard mutex_guard(MQTT_Manager::_mqtt_client_mutex); + WebsocketServer::set_stomp_topic_retained(fmt::format("mqtt/{}", topic), false); if (topic.size() > 0) { mqtt::message_ptr msg = mqtt::make_message(topic.c_str(), "", 0, 0, true); if (MQTT_Manager::_mqtt_client != nullptr && MQTT_Manager::_mqtt_client->is_connected()) { @@ -318,3 +351,13 @@ void MQTT_Manager::clear_retain(const std::string &topic) { } } } + +void MQTT_Manager::_process_stomp_message(StompFrame frame) { + std::string topic = frame.headers["destination"]; + boost::algorithm::replace_all(topic, "mqtt/", ""); + std::string payload = frame.body; + + if (MQTT_Manager::_mqtt_callbacks.count(topic) > 0) { + MQTT_Manager::_mqtt_callbacks[topic](topic, payload); + } +} diff --git a/docker/MQTTManager/include/mqtt_manager/mqtt_manager.hpp b/docker/MQTTManager/include/mqtt_manager/mqtt_manager.hpp index 00c623e2..8f423829 100644 --- a/docker/MQTTManager/include/mqtt_manager/mqtt_manager.hpp +++ b/docker/MQTTManager/include/mqtt_manager/mqtt_manager.hpp @@ -1,6 +1,7 @@ #ifndef MQTT_MANAGER_HPP #define MQTT_MANAGER_HPP +#include "websocket_server/websocket_server.hpp" #include #include #include @@ -24,7 +25,6 @@ struct MQTTMessage { class MQTT_Manager { public: static void init(); // Load config and connect to MQTT - static void connect(); /* @@ -113,6 +113,7 @@ class MQTT_Manager { static inline std::mutex _mqtt_client_mutex; static inline std::mutex _mqtt_message_mutex; static inline std::list _mqtt_messages_buffer; + static inline std::unordered_map _mqtt_retain_buffer; // Used so that when we reconnect to an MQTT server that has restarted we can repopulate the retained messages. static void _reconnect_mqtt_client(); @@ -129,6 +130,14 @@ class MQTT_Manager { static inline std::string _mqtt_username; static inline std::string _mqtt_password; static inline std::atomic _stop_consuming; + + /** + * Will process a STOMP message received the websocket through WebsocketManager. + * This is used to bridge STOMP and MQTT to be able to hook into React web frontend. + * @param topic: The STOMP topic the message was received on. + * @param message: The STOMP message payload. + */ + static void _process_stomp_message(StompFrame frame); }; #endif // !MQTT_MANAGER_HPP diff --git a/docker/MQTTManager/include/mqtt_manager_config/mqtt_manager_config.cpp b/docker/MQTTManager/include/mqtt_manager_config/mqtt_manager_config.cpp index 7abe0236..843cd3d4 100644 --- a/docker/MQTTManager/include/mqtt_manager_config/mqtt_manager_config.cpp +++ b/docker/MQTTManager/include/mqtt_manager_config/mqtt_manager_config.cpp @@ -1,4 +1,5 @@ #include "mqtt_manager_config.hpp" +#include "light/light.hpp" #include "openssl/evp.h" #include "web_helper/WebHelper.hpp" #include @@ -7,14 +8,12 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include #include #include @@ -31,11 +30,6 @@ #include #include -MqttManagerSettingsHolder MqttManagerConfig::get_settings() { - std::lock_guard lock_guard(MqttManagerConfig::_settings_mutex); - return MqttManagerConfig::_settings; -} - void MqttManagerConfig::load() { SPDLOG_TRACE("Loading timezone from /etc/timezone."); // Begin by loading timezone @@ -48,98 +42,169 @@ void MqttManagerConfig::load() { MqttManagerConfig::timezone = timezone_str; SPDLOG_INFO("Read timezone {} from /etc/timezone.", timezone_str); + SPDLOG_DEBUG("Clearing config values cache."); + MqttManagerConfig::_settings_values_cache.clear(); + { SPDLOG_INFO("Loading MQTT Manager settings."); std::lock_guard lock_guard(MqttManagerConfig::_settings_mutex); - MqttManagerConfig::_settings.manager_address = MqttManagerConfig::get_setting_with_default("manager_address", ""); - MqttManagerConfig::_settings.manager_port = std::stoi(MqttManagerConfig::get_setting_with_default("manager_port", "8000")); - MqttManagerConfig::_settings.color_temp_min = std::stoi(MqttManagerConfig::get_setting_with_default("color_temp_min", "2000")); - MqttManagerConfig::_settings.color_temp_max = std::stoi(MqttManagerConfig::get_setting_with_default("color_temp_max", "6000")); - MqttManagerConfig::_settings.reverse_color_temperature_slider = MqttManagerConfig::get_setting_with_default("reverse_color_temp", "False").compare("True") == 0; - MqttManagerConfig::_settings.date_format = MqttManagerConfig::get_setting_with_default("date_format", "%a %d/%m/ %Y"); - MqttManagerConfig::_settings.clock_24_hour_format = MqttManagerConfig::get_setting_with_default("clock_us_style", "False").compare("False") == 0; - MqttManagerConfig::_settings.optimistic_mode = MqttManagerConfig::get_setting_with_default("optimistic_mode", "True").compare("True") == 0; - MqttManagerConfig::_settings.mqtt_wait_time = std::stoi(MqttManagerConfig::get_setting_with_default("mqtt_wait_time", "1000")); - const char *is_home_assistant_addon = std::getenv("IS_HOME_ASSISTANT_ADDON"); if (is_home_assistant_addon != nullptr) { if (std::string(is_home_assistant_addon).compare("true") == 0) { - MqttManagerConfig::_settings.is_home_assistant_addon = true; + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::IS_HOME_ASSISTANT_ADDON, "true"); } else { - MqttManagerConfig::_settings.is_home_assistant_addon = false; + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::IS_HOME_ASSISTANT_ADDON, "false"); } } else { - MqttManagerConfig::_settings.is_home_assistant_addon = false; + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::IS_HOME_ASSISTANT_ADDON, "false"); } - std::string turn_on_bevaiour = MqttManagerConfig::get_setting_with_default("turn_on_behaviour", "color_temp"); + std::string turn_on_bevaiour = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::TURN_ON_BEHAVIOR); if (turn_on_bevaiour.compare("color_temp") == 0) { - MqttManagerConfig::_settings.light_turn_on_behaviour = LightTurnOnBehaviour::COLOR_TEMPERATURE; + MqttManagerConfig::_light_turn_on_behaviour = LightTurnOnBehaviour::COLOR_TEMPERATURE; } else if (turn_on_bevaiour.compare("restore") == 0) { - MqttManagerConfig::_settings.light_turn_on_behaviour = LightTurnOnBehaviour::RESTORE_PREVIOUS; + MqttManagerConfig::_light_turn_on_behaviour = LightTurnOnBehaviour::RESTORE_PREVIOUS; } else { SPDLOG_WARN("Failed to determine turn on bevaiour for lights, assuming color temp. Value set: {}", turn_on_bevaiour); - MqttManagerConfig::_settings.light_turn_on_behaviour = LightTurnOnBehaviour::COLOR_TEMPERATURE; + MqttManagerConfig::_light_turn_on_behaviour = LightTurnOnBehaviour::COLOR_TEMPERATURE; } } MqttManagerConfig::update_firmware_checksum(); MqttManagerConfig::update_tft_checksums(); + MqttManagerConfig::populate_default_and_clean(); + // Notify all listeners that the config has been loaded MqttManagerConfig::_config_loaded_listeners(); } -std::string MqttManagerConfig::get_setting_with_default(std::string key, std::string default_value) { - std::lock_guard lock_guard(MqttManagerConfig::_database_access_mutex); - try { - auto result = database_manager::database.get_all(sqlite_orm::where(sqlite_orm::c(&database_manager::SettingHolder::name) == key)); - if (result.size() > 0) [[likely]] { - SPDLOG_TRACE("Found setting {} with value {}", key, result[0].value); - return result[0].value; - } else { - SPDLOG_TRACE("Did not find setting {}. Returning default: {}", key, default_value); - return default_value; +void MqttManagerConfig::populate_default_and_clean() { + auto settings = database_manager::database.get_all(); + // Check for set settings that are invalid/unused. + for (const auto &setting : settings) { + bool found = false; + for (const auto &setting_pair : MqttManagerConfig::_setting_key_map) { + if (setting_pair.second.first == setting.name) { + found = true; + break; + } + } + // Cleanup old setting. + if (!found) { + SPDLOG_WARN("Removing invalid setting {} from DB.", setting.name); + database_manager::database.remove(setting.id); + } + } + + // Check if all default settings are set. + for (const auto &setting : MqttManagerConfig::_setting_key_map) { + bool found = false; + for (const auto &setting_pair : settings) { + if (setting_pair.name == setting.second.first) { + found = true; + break; + } + } + if (!found) { + SPDLOG_INFO("Setting {} not found in DB. Setting default value: '{}'.", setting.second.first, setting.second.second); + database_manager::SettingHolder new_setting; + new_setting.name = setting.second.first; + new_setting.value = setting.second.second; + database_manager::database.insert(new_setting); } - } catch (std::exception &ex) { - SPDLOG_ERROR("Caught exception while trying to access database to retrieve setting {}. Exception: {}", key, boost::diagnostic_information(ex)); } - SPDLOG_TRACE("Did not find setting {}. Returning default: {}", key, default_value); - return default_value; +} + +void MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING key, std::string value) { + std::string setting_db_key = MqttManagerConfig::_setting_key_map[key].first; + SPDLOG_DEBUG("Setting '{}' to value '{}'", setting_db_key, value); + + auto result = database_manager::database.get_all(sqlite_orm::where(sqlite_orm::c(&database_manager::SettingHolder::name) == setting_db_key)); + if (!result.empty()) { + result[0].value = value; + SPDLOG_DEBUG("Set settings key '{}' to value '{}'", setting_db_key, value); + database_manager::database.update(result[0]); + } else { + SPDLOG_ERROR("Failed to find existing setting for key '{}'. Will create a new key.", setting_db_key); + database_manager::SettingHolder setting; + setting.name = setting_db_key; + setting.value = value; + database_manager::database.insert(setting); + } + + MqttManagerConfig::_settings_values_cache[key] = value; } void MqttManagerConfig::set_nspanel_setting_value(int32_t nspanel_id, std::string key, std::string value) { + using namespace sqlite_orm; SPDLOG_DEBUG("Setting '{}' to value '{}' for NSPanel with ID {}", key, value, nspanel_id); - database_manager::NSPanelSettingHolder setting; - setting.nspanel_id = nspanel_id; - setting.name = key; - setting.value = value; - database_manager::database.insert(setting); + auto result = database_manager::database.get_all(sqlite_orm::where(sqlite_orm::c(&database_manager::NSPanelSettingHolder::name) = key) and sqlite_orm::c(&database_manager::NSPanelSettingHolder::nspanel_id) == nspanel_id); + if (!result.empty()) { + result[0].value = value; + database_manager::database.update(result[0]); + } else { + database_manager::NSPanelSettingHolder setting; + setting.nspanel_id = nspanel_id; + setting.name = key; + setting.value = value; + database_manager::database.insert(setting); + } +} + +bool MqttManagerConfig::is_home_assistant_addon() { + return MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::IS_HOME_ASSISTANT_ADDON); +} + +LightTurnOnBehaviour MqttManagerConfig::get_light_turn_on_behaviour() { + return MqttManagerConfig::_light_turn_on_behaviour; } void MqttManagerConfig::update_firmware_checksum() { std::lock_guard lock_guard(MqttManagerConfig::_md5_checksum_files_mutex); SPDLOG_INFO("Updating/calculating MD5 checksums for all firmware files."); - auto firmware_checksum = MqttManagerConfig::_get_file_md5_checksum("/usr/src/app/nspanelmanager/firmware.bin"); + // Update firmware checksum + auto firmware_checksum = MqttManagerConfig::_get_file_md5_checksum("/usr/src/app/nspanelmanager/firmware/sonoff/firmware.bin"); if (firmware_checksum.has_value()) { - MqttManagerConfig::_md5_checksum_firmware = firmware_checksum.value(); - WebsocketServer::remove_warning("MD5 checksum for firmware was not able to be calculated."); + MqttManagerConfig::_md5_checksum_firmware_sonoff = firmware_checksum.value(); + WebsocketServer::remove_warning("MD5 checksum for sonoff firmware was not able to be calculated."); SPDLOG_INFO("Firmware checksum: {}", firmware_checksum.value()); } else { - WebsocketServer::register_warning(WebsocketServer::ActiveWarningLevel::ERROR, "MD5 checksum for firmware was not able to be calculated."); - SPDLOG_ERROR("Failed to calculate checksum for firmware!"); + WebsocketServer::register_warning(WebsocketServer::ActiveWarningLevel::ERROR, "MD5 checksum for sonoff firmware was not able to be calculated."); + SPDLOG_ERROR("Failed to calculate checksum for sonoff firmware!"); } - auto littlefs_checksum = MqttManagerConfig::_get_file_md5_checksum("/usr/src/app/nspanelmanager/data_file.bin"); + firmware_checksum = MqttManagerConfig::_get_file_md5_checksum("/usr/src/app/nspanelmanager/firmware/custom/firmware.bin"); + if (firmware_checksum.has_value()) { + MqttManagerConfig::_md5_checksum_firmware_custom = firmware_checksum.value(); + WebsocketServer::remove_warning("MD5 checksum for custom firmware was not able to be calculated."); + SPDLOG_INFO("Firmware checksum: {}", firmware_checksum.value()); + } else { + WebsocketServer::register_warning(WebsocketServer::ActiveWarningLevel::ERROR, "MD5 checksum for custom firmware was not able to be calculated."); + SPDLOG_ERROR("Failed to calculate checksum for custom firmware!"); + } + + // Update LittleFS checksum + auto littlefs_checksum = MqttManagerConfig::_get_file_md5_checksum("/usr/src/app/nspanelmanager/firmware/sonoff/data_file.bin"); if (littlefs_checksum.has_value()) { - MqttManagerConfig::_md5_checksum_littlefs = littlefs_checksum.value(); - WebsocketServer::remove_warning("MD5 checksum for littlefs/data file was not able to be calculated."); + MqttManagerConfig::_md5_checksum_littlefs_sonoff = littlefs_checksum.value(); + WebsocketServer::remove_warning("MD5 checksum for sonoff littlefs/data file was not able to be calculated."); SPDLOG_INFO("LittleFS checksum: {}", littlefs_checksum.value()); } else { - WebsocketServer::register_warning(WebsocketServer::ActiveWarningLevel::ERROR, "MD5 checksum for littlefs/data file was not able to be calculated."); + WebsocketServer::register_warning(WebsocketServer::ActiveWarningLevel::ERROR, "MD5 checksum for sonoff littlefs/data file was not able to be calculated."); + SPDLOG_ERROR("Failed to calculate checksum for LittleFS!"); + } + + littlefs_checksum = MqttManagerConfig::_get_file_md5_checksum("/usr/src/app/nspanelmanager/firmware/custom/data_file.bin"); + if (littlefs_checksum.has_value()) { + MqttManagerConfig::_md5_checksum_littlefs_custom = littlefs_checksum.value(); + WebsocketServer::remove_warning("MD5 checksum for custom littlefs/data file was not able to be calculated."); + SPDLOG_INFO("LittleFS checksum: {}", littlefs_checksum.value()); + } else { + WebsocketServer::register_warning(WebsocketServer::ActiveWarningLevel::ERROR, "MD5 checksum for custom littlefs/data file was not able to be calculated."); SPDLOG_ERROR("Failed to calculate checksum for LittleFS!"); } } @@ -319,20 +384,36 @@ std::optional MqttManagerConfig::_get_file_md5_checksum(std::string return boost::algorithm::to_lower_copy(ss.str()); // Convert to lowercase as the md5 checksum calculated in Django is calculated with lower case latters. } -std::expected MqttManagerConfig::get_firmware_checksum() { +std::expected MqttManagerConfig::get_firmware_sonoff_checksum() { + std::lock_guard lock_guard(MqttManagerConfig::_md5_checksum_files_mutex); + if (MqttManagerConfig::_md5_checksum_firmware_sonoff.empty()) { + return std::unexpected(false); + } + return MqttManagerConfig::_md5_checksum_firmware_sonoff; +} + +std::expected MqttManagerConfig::get_firmware_custom_checksum() { std::lock_guard lock_guard(MqttManagerConfig::_md5_checksum_files_mutex); - if (MqttManagerConfig::_md5_checksum_firmware.empty()) { + if (MqttManagerConfig::_md5_checksum_firmware_custom.empty()) { return std::unexpected(false); } - return MqttManagerConfig::_md5_checksum_firmware; + return MqttManagerConfig::_md5_checksum_firmware_custom; } -std::expected MqttManagerConfig::get_littlefs_checksum() { +std::expected MqttManagerConfig::get_littlefs_sonoff_checksum() { std::lock_guard lock_guard(MqttManagerConfig::_md5_checksum_files_mutex); - if (MqttManagerConfig::_md5_checksum_littlefs.empty()) { + if (MqttManagerConfig::_md5_checksum_littlefs_sonoff.empty()) { return std::unexpected(false); } - return MqttManagerConfig::_md5_checksum_littlefs; + return MqttManagerConfig::_md5_checksum_littlefs_sonoff; +} + +std::expected MqttManagerConfig::get_littlefs_custom_checksum() { + std::lock_guard lock_guard(MqttManagerConfig::_md5_checksum_files_mutex); + if (MqttManagerConfig::_md5_checksum_littlefs_custom.empty()) { + return std::unexpected(false); + } + return MqttManagerConfig::_md5_checksum_littlefs_custom; } std::expected MqttManagerConfig::get_eu_tft1_checksum() { @@ -430,3 +511,25 @@ std::expected MqttManagerConfig::get_us_horizontal_mirrored_t } return MqttManagerConfig::_md5_checksum_us_horizontal_mirrored_tft4; } + +// TESTING +#if defined(TEST_MODE) && TEST_MODE == 1 +#include + +TEST(MqttManagerConfigTest, verify_all_settings_exists_and_have_db_key) { + for (int i = 0; i < static_cast(MQTT_MANAGER_SETTING::LAST); i++) { + MQTT_MANAGER_SETTING setting = static_cast(i); + bool found = MqttManagerConfig::_setting_key_map.contains(setting); + EXPECT_TRUE(found) << "Setting (enum value) " << i << " not found in the settings list. Cannot test if it has a setting key."; + if (found) { + EXPECT_TRUE(!MqttManagerConfig::_setting_key_map[setting].first.empty()) << "Setting (enum value) " << i << " does not name a DB key."; + } + } +} + +TEST(MqttManagerConfigTest, verify_settings_cache_is_used) { + bool value = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::CLOCK_US_STYLE); + EXPECT_TRUE(MqttManagerConfig::_settings_values_cache.contains(MQTT_MANAGER_SETTING::CLOCK_US_STYLE)); +} + +#endif diff --git a/docker/MQTTManager/include/mqtt_manager_config/mqtt_manager_config.hpp b/docker/MQTTManager/include/mqtt_manager_config/mqtt_manager_config.hpp index 0b978b64..fb699749 100644 --- a/docker/MQTTManager/include/mqtt_manager_config/mqtt_manager_config.hpp +++ b/docker/MQTTManager/include/mqtt_manager_config/mqtt_manager_config.hpp @@ -1,59 +1,123 @@ #ifndef MQTTMANAGER_CONFIG_HPP #define MQTTMANAGER_CONFIG_HPP +#include "light/light.hpp" +#include +#include #include +#include #include #include #include #include #include +#include #include +#include -#define MANAGER_ADDRESS "127.0.0.1" -#define MANAGER_PORT "8000" +#if defined(TEST_MODE) && TEST_MODE == 1 +#include +#endif enum LightTurnOnBehaviour { COLOR_TEMPERATURE, RESTORE_PREVIOUS, }; -struct MqttManagerSettingsHolder { - std::string mqtt_server = ""; - uint16_t mqtt_server_port = 1883; - - uint32_t color_temp_min = 0; - uint32_t color_temp_max = 0; - bool reverse_color_temperature_slider = false; - std::string date_format = ""; - bool clock_24_hour_format = true; - uint16_t max_log_buffer_size = 100; - uint16_t manager_port = 8000; - std::string manager_address = ""; - bool is_home_assistant_addon = false; // Are we running as standalone docker or as an Home Assistant addon? - - bool optimistic_mode; // Should we assume the values we set on lights are correct or do we wait for confirmation of change. - uint16_t mqtt_wait_time; // For how long should panels ignore MQTT feedback after change. TODO: Is this used? - - LightTurnOnBehaviour light_turn_on_behaviour; // When turning a light on, should we restore previous setting or assume the user wants color temperature? +enum MQTT_MANAGER_SETTING { + BUTTON_LONG_PRESS_TIME, + CLOCK_US_STYLE, + COLOR_TEMP_MAX, + COLOR_TEMP_MIN, + THEME, + DATE_FORMAT, + IS_HOME_ASSISTANT_ADDON, + HOME_ASSISTANT_ADDRESS, + HOME_ASSISTANT_TOKEN, + LOCATION_LATITUDE, + LOCATION_LONGITUDE, + MANAGER_ADDRESS, + MANAGER_PORT, + MAX_LIVE_LOG_MESSAGES, + MAX_LOG_BUFFER_SIZE, + MIN_BUTTON_PUSH_TIME, + MQTTMANAGER_LOG_LEVEL, + MQTT_PASSWORD, + MQTT_PORT, + MQTT_SERVER, + MQTT_USERNAME, + OPENHAB_ADDRESS, + OPENHAB_TOKEN, + OPENHAB_BRIGHTNESS_CHANNEL_MAX, + OPENHAB_BRIGHTNESS_CHANNEL_MIN, + OPENHAB_BRIGHTNESS_CHANNEL_NAME, + OPENHAB_COLOR_TEMP_CHANNEL_NAME, + OPENHAB_RGB_CHANNEL_NAME, + OUTSIDE_TEMP_SENSOR_ENTITY_ID, + OUTSIDE_TEMP_SENSOR_PROVIDER, + WEATHER_PRECIPITATION_FORMAT, + RAISE_TO_100_LIGHT_LEVEL, + ALL_ROOMS_STATUS_BACKOFF_TIME, + REVERSE_COLOR_TEMP, + SCREEN_DIM_LEVEL, + SCREENSAVER_ACTIVATION_TIMEOUT, + SCREENSAVER_DIM_LEVEL, + SCREENSAVER_MODE, + SHOW_SCREENSAVER_INSIDE_TEMPERATURE, + SHOW_SCREENSAVER_OUTSIDE_TEMPERATURE, + SPECIAL_MODE_RELEASE_TIME, + SPECIAL_MODE_TRIGGER_TIME, + TURN_ON_BEHAVIOR, + USE_FAHRENHEIT, + WEATHER_UPDATE_INTERVAL, + WEATHER_WIND_SPEED_FORMAT, + MQTT_WAIT_TIME, + OPTIMISTIC_MODE, + LIGHT_TURN_ON_BRIGHTNESS, + DEFAULT_NSPANEL_TYPE, + LAST, // Keep last as to be able to reference the last element in the list. }; class MqttManagerConfig { public: + // Initialize this static library with settings and configuration options static void load(); - static MqttManagerSettingsHolder get_settings(); + + // Populate the database with default values in case they are not already set + // and clean up any invalid or outdated settings. + static void populate_default_and_clean(); + static inline std::string timezone; - static std::string get_setting_with_default(std::string key, std::string default_value); + template + static T get_setting_with_default(MQTT_MANAGER_SETTING key); + + /** + * Set the value of a setting key in the DB and update the in-memory cache. + */ + static void set_setting_value(MQTT_MANAGER_SETTING key, std::string value); /** * Set a value for a NSPanel setting. */ static void set_nspanel_setting_value(int32_t nspanel_id, std::string key, std::string default_value); + /** + * Check if we are running in Home Assistant addon mode. If so, return true. + */ + static bool is_home_assistant_addon(); + + /* + * Get the light turn on behaviour. + */ + static LightTurnOnBehaviour get_light_turn_on_behaviour(); + static void update_firmware_checksum(); static void update_tft_checksums(); - static std::expected get_firmware_checksum(); - static std::expected get_littlefs_checksum(); + static std::expected get_firmware_sonoff_checksum(); + static std::expected get_firmware_custom_checksum(); + static std::expected get_littlefs_sonoff_checksum(); + static std::expected get_littlefs_custom_checksum(); static std::expected get_eu_tft1_checksum(); static std::expected get_eu_tft2_checksum(); static std::expected get_eu_tft3_checksum(); @@ -92,12 +156,15 @@ class MqttManagerConfig { static inline boost::signals2::signal _config_loaded_listeners; static inline std::mutex _settings_mutex; - static inline MqttManagerSettingsHolder _settings; + + static inline LightTurnOnBehaviour _light_turn_on_behaviour = LightTurnOnBehaviour::COLOR_TEMPERATURE; static std::optional _get_file_md5_checksum(std::string file_path); static inline std::mutex _md5_checksum_files_mutex; - static inline std::string _md5_checksum_firmware; - static inline std::string _md5_checksum_littlefs; + static inline std::string _md5_checksum_firmware_sonoff; + static inline std::string _md5_checksum_firmware_custom; + static inline std::string _md5_checksum_littlefs_sonoff; + static inline std::string _md5_checksum_littlefs_custom; static inline std::string _md5_checksum_eu_tft1; static inline std::string _md5_checksum_eu_tft2; static inline std::string _md5_checksum_eu_tft3; @@ -110,6 +177,152 @@ class MqttManagerConfig { static inline std::string _md5_checksum_us_horizontal_mirrored_tft2; static inline std::string _md5_checksum_us_horizontal_mirrored_tft3; static inline std::string _md5_checksum_us_horizontal_mirrored_tft4; + + static inline std::unordered_map _settings_values_cache; + + // Map of SETTING key that points to a pair of two strings. Pair.first is database key as string, Pair.second is default value. + static inline std::unordered_map> _setting_key_map = { + {MQTT_MANAGER_SETTING::IS_HOME_ASSISTANT_ADDON, {"is_home_assistant_addon", "false"}}, + {MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, {"optimistic_mode", "false"}}, + {MQTT_MANAGER_SETTING::MQTT_WAIT_TIME, {"mqtt_wait_time", "0"}}, + {MQTT_MANAGER_SETTING::BUTTON_LONG_PRESS_TIME, {"button_long_press_time", "5000"}}, + {MQTT_MANAGER_SETTING::CLOCK_US_STYLE, {"clock_us_style", "False"}}, + {MQTT_MANAGER_SETTING::COLOR_TEMP_MAX, {"color_temp_max", "6000"}}, + {MQTT_MANAGER_SETTING::COLOR_TEMP_MIN, {"color_temp_min", "2000"}}, + {MQTT_MANAGER_SETTING::THEME, {"theme", "default"}}, + {MQTT_MANAGER_SETTING::DATE_FORMAT, {"date_format", "%a %d/%m %Y"}}, + {MQTT_MANAGER_SETTING::HOME_ASSISTANT_ADDRESS, {"home_assistant_address", ""}}, + {MQTT_MANAGER_SETTING::HOME_ASSISTANT_TOKEN, {"home_assistant_token", ""}}, + {MQTT_MANAGER_SETTING::LOCATION_LATITUDE, {"location_latitude", ""}}, + {MQTT_MANAGER_SETTING::LOCATION_LONGITUDE, {"location_longitude", ""}}, + {MQTT_MANAGER_SETTING::MANAGER_ADDRESS, {"manager_address", ""}}, + {MQTT_MANAGER_SETTING::MANAGER_PORT, {"manager_port", ""}}, + {MQTT_MANAGER_SETTING::MAX_LIVE_LOG_MESSAGES, {"max_live_log_messages", "10"}}, + {MQTT_MANAGER_SETTING::MAX_LOG_BUFFER_SIZE, {"max_log_buffer_size", "10"}}, + {MQTT_MANAGER_SETTING::MIN_BUTTON_PUSH_TIME, {"min_button_push_time", "50"}}, + {MQTT_MANAGER_SETTING::MQTTMANAGER_LOG_LEVEL, {"mqttmanager_log_level", "debug"}}, + {MQTT_MANAGER_SETTING::MQTT_PASSWORD, {"mqtt_password", ""}}, + {MQTT_MANAGER_SETTING::MQTT_PORT, {"mqtt_port", "1883"}}, + {MQTT_MANAGER_SETTING::MQTT_SERVER, {"mqtt_server", ""}}, + {MQTT_MANAGER_SETTING::MQTT_USERNAME, {"mqtt_username", ""}}, + {MQTT_MANAGER_SETTING::OPENHAB_ADDRESS, {"openhab_address", ""}}, + {MQTT_MANAGER_SETTING::OPENHAB_TOKEN, {"openhab_token", ""}}, + {MQTT_MANAGER_SETTING::OPENHAB_BRIGHTNESS_CHANNEL_MAX, {"openhab_brightness_channel_max", "255"}}, + {MQTT_MANAGER_SETTING::OPENHAB_BRIGHTNESS_CHANNEL_MIN, {"openhab_brightness_channel_min", "0"}}, + {MQTT_MANAGER_SETTING::OUTSIDE_TEMP_SENSOR_ENTITY_ID, {"outside_temp_sensor_entity_id", ""}}, + {MQTT_MANAGER_SETTING::OUTSIDE_TEMP_SENSOR_PROVIDER, {"outside_temp_sensor_provider", ""}}, + {MQTT_MANAGER_SETTING::WEATHER_PRECIPITATION_FORMAT, {"weather_precipitation_format", "mm"}}, + {MQTT_MANAGER_SETTING::RAISE_TO_100_LIGHT_LEVEL, {"raise_to_100_light_level", "95"}}, + {MQTT_MANAGER_SETTING::ALL_ROOMS_STATUS_BACKOFF_TIME, {"all_rooms_status_backoff_time", "250"}}, + {MQTT_MANAGER_SETTING::REVERSE_COLOR_TEMP, {"reverse_color_temp", "False"}}, + {MQTT_MANAGER_SETTING::SCREEN_DIM_LEVEL, {"screen_dim_level", "100"}}, + {MQTT_MANAGER_SETTING::SCREENSAVER_ACTIVATION_TIMEOUT, {"screensaver_activation_timeout", "30000"}}, + {MQTT_MANAGER_SETTING::SCREENSAVER_DIM_LEVEL, {"screensaver_dim_level", "1"}}, + {MQTT_MANAGER_SETTING::SCREENSAVER_MODE, {"screensaver_mode", "with_background"}}, + {MQTT_MANAGER_SETTING::SHOW_SCREENSAVER_INSIDE_TEMPERATURE, {"show_screensaver_inside_temperature", "True"}}, + {MQTT_MANAGER_SETTING::SHOW_SCREENSAVER_OUTSIDE_TEMPERATURE, {"show_screensaver_outside_temperature", "True"}}, + {MQTT_MANAGER_SETTING::SPECIAL_MODE_RELEASE_TIME, {"special_mode_release_time", "5000"}}, + {MQTT_MANAGER_SETTING::SPECIAL_MODE_TRIGGER_TIME, {"special_mode_trigger_time", "300"}}, + {MQTT_MANAGER_SETTING::TURN_ON_BEHAVIOR, {"turn_on_behavior", "color_temp"}}, + {MQTT_MANAGER_SETTING::USE_FAHRENHEIT, {"use_fahrenheit", "False"}}, + {MQTT_MANAGER_SETTING::WEATHER_UPDATE_INTERVAL, {"weather_update_interval", "10"}}, + {MQTT_MANAGER_SETTING::WEATHER_WIND_SPEED_FORMAT, {"weather_wind_speed_format", "kmh"}}, + {MQTT_MANAGER_SETTING::MQTT_WAIT_TIME, {"mqtt_wait_time", "1000"}}, + {MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, {"optimistic_mode", "True"}}, + {MQTT_MANAGER_SETTING::LIGHT_TURN_ON_BRIGHTNESS, {"light_turn_on_brightness", "50"}}, + {MQTT_MANAGER_SETTING::DEFAULT_NSPANEL_TYPE, {"default_nspanel_type", "eu"}}, +#if defined(TEST_MODE) && TEST_MODE == 1 + {MQTT_MANAGER_SETTING::LAST, {"last", "last_value"}}, +#endif + }; + +#if defined(TEST_MODE) && TEST_MODE == 1 + FRIEND_TEST(MqttManagerConfigTest, verify_all_settings_exists_and_have_db_key); + FRIEND_TEST(MqttManagerConfigTest, verify_settings_cache_is_used); +#endif }; +template <> +inline std::string MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING key) { + std::lock_guard lock_guard(MqttManagerConfig::_database_access_mutex); + if (MqttManagerConfig::_settings_values_cache.contains(key)) { + return MqttManagerConfig::_settings_values_cache[key]; + } + + std::string setting_db_key = MqttManagerConfig::_setting_key_map[key].first; + try { + auto result = database_manager::database.get_all(sqlite_orm::where(sqlite_orm::c(&database_manager::SettingHolder::name) == setting_db_key)); + if (result.size() > 0) [[likely]] { + SPDLOG_TRACE("Found setting {} with value {}", setting_db_key, result[0].value); + MqttManagerConfig::_settings_values_cache[key] = result[0].value; + return result[0].value; + } else { + SPDLOG_TRACE("Did not find setting {} in database. Looking for default value.", setting_db_key); + + if (MqttManagerConfig::_setting_key_map.contains(key)) { + std::string value = MqttManagerConfig::_setting_key_map[key].second; + SPDLOG_TRACE("Returning default value '{}' for setting '{}'", value, setting_db_key); + MqttManagerConfig::_settings_values_cache[key] = value; + return value; + } else { + SPDLOG_ERROR("Did not find default setting value for key '{}'. Returning empty string.", setting_db_key); + return ""; + } + } + } catch (std::exception &ex) { + SPDLOG_ERROR("Caught exception while trying to access database to retrieve setting {}. Exception: {}", setting_db_key, boost::diagnostic_information(ex)); + } + SPDLOG_TRACE("Did not find setting {}. Returning empty string.", setting_db_key); + return ""; +} + +template <> +inline bool MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING key) { + std::string value = MqttManagerConfig::get_setting_with_default(key); + boost::to_lower(value); + return value.compare("true") == 0; +} + +template <> +inline int32_t MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING key) { + std::string value = MqttManagerConfig::get_setting_with_default(key); + try { + return std::stoi(value); + } catch (std::invalid_argument &ex) { + SPDLOG_ERROR("Caught exception while trying to convert string to integer. Returning 0. Key: {}, Exception: {}", MqttManagerConfig::_setting_key_map[key].first, boost::diagnostic_information(ex)); + return 0; + } catch (std::out_of_range &ex) { + SPDLOG_ERROR("Caught exception while trying to convert string to integer. Returning 0. Key: {}, Exception: {}", MqttManagerConfig::_setting_key_map[key].first, boost::diagnostic_information(ex)); + return 0; + } +} + +template <> +inline uint32_t MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING key) { + std::string value = MqttManagerConfig::get_setting_with_default(key); + try { + return std::stoul(value); + } catch (std::invalid_argument &ex) { + SPDLOG_ERROR("Caught exception while trying to convert string to unsigned integer. Key: {}, Returning 0. Exception: {}", MqttManagerConfig::_setting_key_map[key].first, boost::diagnostic_information(ex)); + return 0; + } catch (std::out_of_range &ex) { + SPDLOG_ERROR("Caught exception while trying to convert string to unsigned integer. Key: {}, Returning 0. Exception: {}", MqttManagerConfig::_setting_key_map[key].first, boost::diagnostic_information(ex)); + return 0; + } +} + +template <> +inline float MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING key) { + std::string value = MqttManagerConfig::get_setting_with_default(key); + try { + return std::stof(value); + } catch (std::invalid_argument &ex) { + SPDLOG_ERROR("Caught exception while trying to convert string to float. Returning 0.0f. Key: {}, Exception: {}", MqttManagerConfig::_setting_key_map[key].first, boost::diagnostic_information(ex)); + return 0.0f; + } catch (std::out_of_range &ex) { + SPDLOG_ERROR("Caught exception while trying to convert string to float. Returning 0.0f. Key: {}, Exception: {}", MqttManagerConfig::_setting_key_map[key].first, boost::diagnostic_information(ex)); + return 0.0f; + } +} + #endif // !MQTTMANAGER_CONFIG_HPP diff --git a/docker/MQTTManager/include/nextion_image_server/nextion_image_server.cpp b/docker/MQTTManager/include/nextion_image_server/nextion_image_server.cpp new file mode 100644 index 00000000..8e4939b9 --- /dev/null +++ b/docker/MQTTManager/include/nextion_image_server/nextion_image_server.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include + +void NextionImageServer::start() { + if (NextionImageServer::_server == nullptr) { + SPDLOG_DEBUG("Creating new ix::HTTPServer."); + NextionImageServer::_server = new ix::HttpServer(8003, "0.0.0.0"); + + NextionImageServer::_server->setOnConnectionCallback( + [](ix::HttpRequestPtr request, + std::shared_ptr connectionState) -> ix::HttpResponsePtr { + // Build a string for the response + std::stringstream ss; + ss << connectionState->getRemoteIp() + << " " + << request->method + << " " + << request->uri + << std::endl; + + std::string content = ss.str(); + + return std::make_shared(200, "OK", + ix::HttpErrorCode::Ok, + ix::WebSocketHttpHeaders(), + content); + }); + } + auto res = _server->listen(); + if (!res.first) { + std::cerr << res.second << std::endl; + return; + } + NextionImageServer::_server->start(); + NextionImageServer::_server->wait(); +} diff --git a/docker/MQTTManager/include/nextion_image_server/nextion_image_server.hpp b/docker/MQTTManager/include/nextion_image_server/nextion_image_server.hpp new file mode 100644 index 00000000..47b5e130 --- /dev/null +++ b/docker/MQTTManager/include/nextion_image_server/nextion_image_server.hpp @@ -0,0 +1,17 @@ +#ifndef MQTTMANAGER_NEXTION_IMAGE_SERVER_HPP +#define MQTTMANAGER_NEXTION_IMAGE_SERVER_HPP + +#include + +class NextionImageServer { +public: + /** + * Start the websocket server and bind to 0.0.0.0:8003. + */ + static void start(); + +private: + static inline ix::HttpServer *_server; +}; + +#endif diff --git a/docker/MQTTManager/include/nspanel/nspanel.cpp b/docker/MQTTManager/include/nspanel/nspanel.cpp index 653920d6..8a3b0f81 100644 --- a/docker/MQTTManager/include/nspanel/nspanel.cpp +++ b/docker/MQTTManager/include/nspanel/nspanel.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -67,11 +68,27 @@ std::shared_ptr NSPanel::create_from_discovery_request(nlohmann::json r database_manager::NSPanel panel_data; panel_data.mac_address = request_data.at("mac_origin").get(); panel_data.friendly_name = request_data.at("friendly_name").get(); + if (request_data.contains("model")) { + std::string nspanel_model = request_data.at("model").get(); + if (nspanel_model.compare("sonoff") == 0) { + panel_data.model = "sonoff"; + } else if (nspanel_model.compare("custom") == 0) { + panel_data.model = "custom"; + } else if (nspanel_model.compare("web") == 0) { + panel_data.model = "web"; + } else { + SPDLOG_WARN("Failed to parse panel model for NSPanel {}. Got value '{}'. Will assume sonoff.", panel_data.friendly_name, panel_data.model); + panel_data.model = "sonoff"; + } + } else { + SPDLOG_WARN("No model field set in request request for NSPanel {}. Got value '{}'. Will assume sonoff.", panel_data.friendly_name, panel_data.model); + panel_data.model = "sonoff"; + } panel_data.room_id = db_room[0].id; panel_data.version = request_data.at("version").get(); - panel_data.button1_detached_mode_light_id = std::nullopt; + panel_data.button1_detached_mode_entity_id = std::nullopt; panel_data.button1_mode = 0; - panel_data.button2_detached_mode_light_id = std::nullopt; + panel_data.button2_detached_mode_entity_id = std::nullopt; panel_data.button2_mode = 0; panel_data.md5_data_file = request_data.at("md5_data_file").get(); panel_data.md5_firmware = request_data.at("md5_firmware").get(); @@ -80,7 +97,7 @@ std::shared_ptr NSPanel::create_from_discovery_request(nlohmann::json r panel_data.accepted = false; try { int new_nspanel_id = database_manager::database.insert(panel_data); - if (MqttManagerConfig::get_setting_with_default("default_nspanel_type", "eu").compare("eu") == 0) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::DEFAULT_NSPANEL_TYPE).compare("eu") == 0) { MqttManagerConfig::set_nspanel_setting_value(new_nspanel_id, "is_us_panel", "False"); } else { MqttManagerConfig::set_nspanel_setting_value(new_nspanel_id, "is_us_panel", "True"); @@ -108,6 +125,17 @@ void NSPanel::reload_config() { this->_settings = panel_settings; this->_has_registered_to_manager = true; // We managed to get the object in above statement and did not throw, ie. has been registered in manager and has an ID in DB. this->_mac = panel_settings.mac_address; + if (panel_settings.model.compare("sonoff") == 0) { + this->_model = MQTT_MANAGER_NSPANEL_MODEL::SONOFF; + } else if (panel_settings.model.compare("custom") == 0) { + this->_model = MQTT_MANAGER_NSPANEL_MODEL::CUSTOM; + } else if (panel_settings.model.compare("web") == 0) { + this->_model = MQTT_MANAGER_NSPANEL_MODEL::WEB; + } else { + SPDLOG_ERROR("Failed to prase panel model for NSPanel {}::{}. Got value '{}'. Will assume sonoff.", panel_settings.model, panel_settings.id, panel_settings.friendly_name); + this->_model = MQTT_MANAGER_NSPANEL_MODEL::SONOFF; + } + this->_is_us_panel = this->_get_nspanel_setting_with_default("is_us_panel", "False").compare("True") == 0; std::string us_panel_orientation = this->_get_nspanel_setting_with_default("us_panel_orientation", "vertical"); if (us_panel_orientation.compare("vertical") == 0) { @@ -200,6 +228,8 @@ void NSPanel::reload_config() { this->_mqtt_status_topic = fmt::format("nspanel/{}/status", this->_mac); this->_mqtt_status_report_topic = fmt::format("nspanel/{}/status_report", this->_mac); this->_mqtt_temperature_topic = fmt::format("nspanel/{}/temperature", this->_mac); + this->_mqtt_humidity_topic = fmt::format("nspanel/{}/humidity", this->_mac); + this->_mqtt_pressure_topic = fmt::format("nspanel/{}/pressure", this->_mac); this->_mqtt_topic_home_page_status = fmt::format("nspanel/{}/home_page", this->_mac); this->_mqtt_topic_home_page_all_rooms_status = fmt::format("nspanel/{}/home_page_all", this->_mac); @@ -244,7 +274,6 @@ void NSPanel::send_config() { SPDLOG_INFO("Sending config over MQTT for panel {}::{}", this->_id, this->_name); NSPanelConfig config; - MqttManagerSettingsHolder global_setting = MqttManagerConfig::get_settings(); auto default_room = EntityManager::get_room(this->_settings.room_id); if (!default_room) { @@ -256,31 +285,40 @@ void NSPanel::send_config() { config.set_name(this->_name); config.set_default_room(this->_settings.room_id); config.set_default_page(static_cast(std::stoi(this->_get_nspanel_setting_with_default("default_page", "0")))); - config.set_min_button_push_time(std::stoi(MqttManagerConfig::get_setting_with_default("min_button_push_time", "50"))); - config.set_button_long_press_time(std::stoi(MqttManagerConfig::get_setting_with_default("button_long_press_time", "5000"))); - config.set_special_mode_trigger_time(std::stoi(MqttManagerConfig::get_setting_with_default("special_mode_trigger_time", "300"))); - config.set_special_mode_release_time(std::stoi(MqttManagerConfig::get_setting_with_default("special_mode_release_time", "5000"))); - config.set_screen_dim_level(std::stoi(this->_get_nspanel_setting_with_default("screen_dim_level", MqttManagerConfig::get_setting_with_default("screen_dim_level", "100")))); - config.set_screensaver_dim_level(std::stoi(this->_get_nspanel_setting_with_default("screensaver_dim_level", MqttManagerConfig::get_setting_with_default("screensaver_dim_level", "1")))); - config.set_screensaver_activation_timeout(std::stoi(this->_get_nspanel_setting_with_default("screensaver_activation_timeout", MqttManagerConfig::get_setting_with_default("screensaver_activation_timeout", "30000")))); - config.set_clock_us_style(!global_setting.clock_24_hour_format); - config.set_use_fahrenheit(MqttManagerConfig::get_setting_with_default("use_fahrenheit", "False").compare("True") == 0); + config.set_min_button_push_time(MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MIN_BUTTON_PUSH_TIME)); + config.set_button_long_press_time(MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::BUTTON_LONG_PRESS_TIME)); + config.set_special_mode_trigger_time(MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::SPECIAL_MODE_TRIGGER_TIME)); + config.set_special_mode_release_time(MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::SPECIAL_MODE_RELEASE_TIME)); + config.set_screen_dim_level(std::stoi(this->_get_nspanel_setting_with_default("screen_dim_level", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::SCREEN_DIM_LEVEL)))); + config.set_screensaver_dim_level(std::stoi(this->_get_nspanel_setting_with_default("screensaver_dim_level", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::SCREENSAVER_DIM_LEVEL)))); + config.set_screensaver_activation_timeout(std::stoi(this->_get_nspanel_setting_with_default("screensaver_activation_timeout", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::SCREENSAVER_ACTIVATION_TIMEOUT)))); + config.set_clock_us_style(MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::CLOCK_US_STYLE)); + config.set_use_fahrenheit(MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::USE_FAHRENHEIT)); config.set_is_us_panel(this->_get_nspanel_setting_with_default("is_us_panel", "False").compare("True") == 0); config.set_reverse_relays(this->_get_nspanel_setting_with_default("reverse_relays", "False").compare("True") == 0); config.set_relay1_default_mode(this->_get_nspanel_setting_with_default("relay1_default_mode", "False").compare("True") == 0); config.set_relay2_default_mode(this->_get_nspanel_setting_with_default("relay2_default_mode", "False").compare("True") == 0); config.set_temperature_calibration((std::stof(this->_get_nspanel_setting_with_default("temperature_calibration", "0.0")) * 10)); - config.set_default_light_brightess(std::stoi(MqttManagerConfig::get_setting_with_default("light_turn_on_brightness", "50"))); + config.set_default_light_brightess(MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::LIGHT_TURN_ON_BRIGHTNESS)); config.set_locked_to_default_room(this->is_locked_to_default_room()); - if ((*default_room)->has_temperature_sensor()) { - config.set_inside_temperature_sensor_mqtt_topic((*default_room)->get_temperature_sensor_mqtt_topic()); - } + config.set_button1_lower_temperature(0); + config.set_button1_upper_temperature(0); + config.set_button2_lower_temperature(0); + config.set_button2_upper_temperature(0); ButtonMode b1_mode = static_cast(this->_settings.button1_mode); if (b1_mode == ButtonMode::DIRECT) { config.set_button1_mode(NSPanelConfig_NSPanelButtonMode_DIRECT); } else if (b1_mode == ButtonMode::FOLLOW) { config.set_button1_mode(NSPanelConfig_NSPanelButtonMode_FOLLOW); + } else if (b1_mode == ButtonMode::THERMOSTAT_HEATING) { + config.set_button1_mode(NSPanelConfig_NSPanelButtonMode_THERMOSTAT_HEAT); + config.set_button1_lower_temperature(std::stoi(this->_get_nspanel_setting_with_default("button1_relay_lower_temperature", "0"))); + config.set_button1_upper_temperature(std::stoi(this->_get_nspanel_setting_with_default("button1_relay_upper_temperature", "0"))); + } else if (b1_mode == ButtonMode::THERMOSTAT_COOLING) { + config.set_button1_mode(NSPanelConfig_NSPanelButtonMode_THERMOSTAT_COOL); + config.set_button1_lower_temperature(std::stoi(this->_get_nspanel_setting_with_default("button1_relay_lower_temperature", "0"))); + config.set_button1_upper_temperature(std::stoi(this->_get_nspanel_setting_with_default("button1_relay_upper_temperature", "0"))); } else { config.set_button1_mode(NSPanelConfig_NSPanelButtonMode_NOTIFY_MANAGER); } @@ -290,37 +328,70 @@ void NSPanel::send_config() { config.set_button2_mode(NSPanelConfig_NSPanelButtonMode_DIRECT); } else if (b2_mode == ButtonMode::FOLLOW) { config.set_button2_mode(NSPanelConfig_NSPanelButtonMode_FOLLOW); + } else if (b2_mode == ButtonMode::THERMOSTAT_HEATING) { + config.set_button2_mode(NSPanelConfig_NSPanelButtonMode_THERMOSTAT_HEAT); + config.set_button2_lower_temperature(std::stoi(this->_get_nspanel_setting_with_default("button2_relay_lower_temperature", "0"))); + config.set_button2_upper_temperature(std::stoi(this->_get_nspanel_setting_with_default("button2_relay_upper_temperature", "0"))); + } else if (b2_mode == ButtonMode::THERMOSTAT_COOLING) { + config.set_button2_mode(NSPanelConfig_NSPanelButtonMode_THERMOSTAT_COOL); + config.set_button2_lower_temperature(std::stoi(this->_get_nspanel_setting_with_default("button2_relay_lower_temperature", "0"))); + config.set_button2_upper_temperature(std::stoi(this->_get_nspanel_setting_with_default("button2_relay_upper_temperature", "0"))); } else { config.set_button2_mode(NSPanelConfig_NSPanelButtonMode_NOTIFY_MANAGER); } - config.set_optimistic_mode(global_setting.optimistic_mode); - config.set_raise_light_level_to_100_above(std::stoi(MqttManagerConfig::get_setting_with_default("raise_to_100_light_level", "96"))); + config.set_optimistic_mode(MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)); + config.set_raise_light_level_to_100_above(MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::RAISE_TO_100_LIGHT_LEVEL)); - std::string screensaver_mode = this->_get_nspanel_setting_with_default("screensaver_mode", MqttManagerConfig::get_setting_with_default("screensaver_mode", "with_background")); + std::string screensaver_mode = this->_get_nspanel_setting_with_default("screensaver_mode", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::SCREENSAVER_MODE)); if (screensaver_mode.compare("with_background") == 0) { config.set_screensaver_mode(NSPanelConfig_NSPanelScreensaverMode::NSPanelConfig_NSPanelScreensaverMode_WEATHER_WITH_BACKGROUND); + + if (config.screensaver_dim_level() == 0) { + SPDLOG_WARN("Setting screensaver dim level to 10 as a screensaver has been chosen to be displayed but screensaver brightness is set to 0."); + config.set_screensaver_dim_level(10); + } } else if (screensaver_mode.compare("without_background") == 0) { config.set_screensaver_mode(NSPanelConfig_NSPanelScreensaverMode::NSPanelConfig_NSPanelScreensaverMode_WEATHER_WITHOUT_BACKGROUND); + + if (config.screensaver_dim_level() == 0) { + SPDLOG_WARN("Setting screensaver dim level to 10 as a screensaver has been chosen to be displayed but screensaver brightness is set to 0."); + config.set_screensaver_dim_level(10); + } } else if (screensaver_mode.compare("datetime_with_background") == 0) { config.set_screensaver_mode(NSPanelConfig_NSPanelScreensaverMode::NSPanelConfig_NSPanelScreensaverMode_DATETIME_WITH_BACKGROUND); + + if (config.screensaver_dim_level() == 0) { + SPDLOG_WARN("Setting screensaver dim level to 10 as a screensaver has been chosen to be displayed but screensaver brightness is set to 0."); + config.set_screensaver_dim_level(10); + } } else if (screensaver_mode.compare("datetime_without_background") == 0) { config.set_screensaver_mode(NSPanelConfig_NSPanelScreensaverMode::NSPanelConfig_NSPanelScreensaverMode_DATETIME_WITHOUT_BACKGROUND); + + if (config.screensaver_dim_level() == 0) { + SPDLOG_WARN("Setting screensaver dim level to 10 as a screensaver has been chosen to be displayed but screensaver brightness is set to 0."); + config.set_screensaver_dim_level(10); + } } else if (screensaver_mode.compare("no_screensaver") == 0) { config.set_screensaver_mode(NSPanelConfig_NSPanelScreensaverMode::NSPanelConfig_NSPanelScreensaverMode_NO_SCREENSAVER); + + if (config.screensaver_dim_level() == 0) { + SPDLOG_WARN("Setting screensaver dim level to 10 as a screensaver has been chosen to be displayed but screensaver brightness is set to 0."); + config.set_screensaver_dim_level(10); + } } else { SPDLOG_ERROR("Unknown screensaver mode '{}' for NSPanel {}::{}, assuming weather with background.", screensaver_mode, this->_id, this->_name); config.set_screensaver_mode(NSPanelConfig_NSPanelScreensaverMode::NSPanelConfig_NSPanelScreensaverMode_WEATHER_WITH_BACKGROUND); } - std::string show_screensaver_inside_temperature = this->_get_nspanel_setting_with_default("show_screensaver_inside_temperature", MqttManagerConfig::get_setting_with_default("show_screensaver_inside_temperature", "True")); + std::string show_screensaver_inside_temperature = this->_get_nspanel_setting_with_default("show_screensaver_inside_temperature", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::SHOW_SCREENSAVER_INSIDE_TEMPERATURE)); if (show_screensaver_inside_temperature.compare("True") == 0) { config.set_show_screensaver_inside_temperature(true); } else { config.set_show_screensaver_inside_temperature(false); } - std::string show_screensaver_outside_temperature = this->_get_nspanel_setting_with_default("show_screensaver_outside_temperature", MqttManagerConfig::get_setting_with_default("show_screensaver_outside_temperature", "True")); + std::string show_screensaver_outside_temperature = this->_get_nspanel_setting_with_default("show_screensaver_outside_temperature", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::SHOW_SCREENSAVER_OUTSIDE_TEMPERATURE)); if (show_screensaver_outside_temperature.compare("True") == 0) { config.set_show_screensaver_outside_temperature(true); } else { @@ -454,6 +525,10 @@ MQTT_MANAGER_NSPANEL_STATE NSPanel::get_state() { return this->_state; } +MQTT_MANAGER_NSPANEL_MODEL NSPanel::get_model() { + return this->_model; +} + void NSPanel::mqtt_callback(std::string topic, std::string payload) { if (payload.empty()) { return; @@ -479,7 +554,7 @@ void NSPanel::mqtt_callback(std::string topic, std::string payload) { std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); std::tm tm = *std::localtime(&now); std::stringstream buffer; - if (MqttManagerConfig::get_settings().clock_24_hour_format) { + if (!MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::CLOCK_US_STYLE)) { buffer << std::put_time(&tm, "%H:%M:%S"); } else { buffer << std::put_time(&tm, "%I:%M:%S %p"); @@ -500,8 +575,8 @@ void NSPanel::mqtt_callback(std::string topic, std::string payload) { // Save log message in backtrace for when (if) the log interface requests it. this->_log_messages_backlog["logs"].insert(this->_log_messages_backlog["logs"].begin(), log_data); // Remove older messages from backtrace. - if (this->_log_messages_backlog["logs"].size() > MqttManagerConfig::get_settings().max_log_buffer_size) { - this->_log_messages_backlog["logs"].erase(this->_log_messages_backlog["logs"].begin() + MqttManagerConfig::get_settings().max_log_buffer_size, this->_log_messages_backlog["logs"].end()); + if (this->_log_messages_backlog["logs"].size() > MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MAX_LOG_BUFFER_SIZE)) { + this->_log_messages_backlog["logs"].erase(this->_log_messages_backlog["logs"].begin() + MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MAX_LOG_BUFFER_SIZE), this->_log_messages_backlog["logs"].end()); } WebsocketServer::update_stomp_topic_value(fmt::format("nspanel/{}/log_backlog", this->_mac), this->_log_messages_backlog); } else { @@ -525,18 +600,6 @@ void NSPanel::mqtt_callback(std::string topic, std::string payload) { } else if (topic.compare(this->_mqtt_status_report_topic) == 0 || topic.compare(fmt::format("nspanel/{}/status_report", this->_name)) == 0) { // TODO: Remove and only use MAC-based topic after 2.0 is stable. NSPanelStatusReport report; if (report.ParseFromString(payload)) { - // Successfully received a new type of status report in protobuf format. This means that we have successfully updated to 2.0 firmware. Remove old MQTT topic retains: - // TODO: Remove once 2.0 is stable release - MQTT_Manager::clear_retain(fmt::format("nspanel/{}/status", this->_name)); - MQTT_Manager::clear_retain(fmt::format("nspanel/{}/status_report", this->_name)); - MQTT_Manager::clear_retain(fmt::format("nspanel/{}/log", this->_name)); - MQTT_Manager::clear_retain(fmt::format("nspanel/{}/r1_state", this->_name)); - MQTT_Manager::clear_retain(fmt::format("nspanel/{}/r2_state", this->_name)); - MQTT_Manager::clear_retain(fmt::format("nspanel/{}/screen_state", this->_name)); - MQTT_Manager::clear_retain(fmt::format("nspanel/{}/temperature_state", this->_name)); - MQTT_Manager::clear_retain(fmt::format("nspanel/{}/command", this->_name)); - MQTT_Manager::clear_retain(fmt::format("nspanel/{}", this->_name)); - SPDLOG_DEBUG("Got new status report from NSPanel {}::{}", this->_id, this->_name); this->_ip_address = report.ip_address(); this->_rssi = report.rssi(); @@ -546,6 +609,14 @@ void NSPanel::mqtt_callback(std::string topic, std::string payload) { this->_current_littlefs_md5_checksum = report.md5_littlefs(); this->_current_tft_md5_checksum = report.md5_tft_gui(); + if (report.has_humidity()) { + this->_humidity = report.humidity(); + } + + if (report.has_pressure()) { + this->_pressure = report.pressure(); + } + switch (report.nspanel_state()) { case NSPanelStatusReport_state::NSPanelStatusReport_state_ONLINE: this->_update_progress = 0; @@ -601,6 +672,12 @@ void NSPanel::mqtt_callback(std::string topic, std::string payload) { // Received new temperature from status report, send out on temperature topic: MQTT_Manager::publish(this->_mqtt_temperature_topic, fmt::format("{:.1f}", this->_temperature)); + if (report.has_humidity()) { + MQTT_Manager::publish(this->_mqtt_humidity_topic, fmt::format("{:.1f}", this->_humidity)); + } + if (report.has_pressure()) { + MQTT_Manager::publish(this->_mqtt_pressure_topic, fmt::format("{:.1f}", this->_pressure)); + } this->send_websocket_status_update(); } else { SPDLOG_ERROR("Failed to parse NSPanelStatusReport from string as protobuf. Will try JSON."); @@ -691,6 +768,11 @@ void NSPanel::mqtt_log_callback(std::string topic, std::string payload) { if (trim_start_pos == std::string::npos) { return; // Message contains no valid chars, only spaces } + + if (payload.length() <= 0) [[unlikely]] { + return; // Message is empty. + } + payload = payload.substr(trim_start_pos, trim_end_pos + 1 - 4); // Trim spaces and such but also the first 7 chars that is the color coding for the message if (payload[0] == 0x1B) { // Message formated with color. Remove color payload = payload.substr(7); @@ -699,7 +781,7 @@ void NSPanel::mqtt_log_callback(std::string topic, std::string payload) { std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); std::tm tm = *std::localtime(&now); std::stringstream buffer; - if (MqttManagerConfig::get_settings().clock_24_hour_format) { + if (!MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::CLOCK_US_STYLE)) { buffer << std::put_time(&tm, "%H:%M:%S"); } else { buffer << std::put_time(&tm, "%I:%M:%S %p"); @@ -724,16 +806,26 @@ void NSPanel::mqtt_log_callback(std::string topic, std::string payload) { return; } + // Convert payload strings non-printable characters to their hex representation + std::string converted_payload; + for (char c : payload) { + if (!std::isprint(c)) { + converted_payload += fmt::format("{{0x{:02X}}}", static_cast(c)); + } else { + converted_payload += c; + } + } + // Remove first char that indicates log level. This is stored separately payload = payload.substr(1); - log_data["message"] = payload; // TODO: Clean up message before sending it out + log_data["message"] = converted_payload; // TODO: Clean up message before sending it out WebsocketServer::update_stomp_topic_value(fmt::format("nspanel/{}/log", this->_mac), log_data.dump()); // Save log message in backtrace for when (if) the log interface requests it. this->_log_messages_backlog["logs"].insert(this->_log_messages_backlog["logs"].begin(), log_data); // Remove older messages from backtrace. - if (this->_log_messages_backlog["logs"].size() > MqttManagerConfig::get_settings().max_log_buffer_size) { - this->_log_messages_backlog["logs"].erase(this->_log_messages_backlog["logs"].begin() + MqttManagerConfig::get_settings().max_log_buffer_size, this->_log_messages_backlog["logs"].end()); + if (this->_log_messages_backlog["logs"].size() > MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MAX_LOG_BUFFER_SIZE)) { + this->_log_messages_backlog["logs"].erase(this->_log_messages_backlog["logs"].begin() + MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MAX_LOG_BUFFER_SIZE), this->_log_messages_backlog["logs"].end()); } WebsocketServer::update_stomp_topic_value(fmt::format("nspanel/{}/log_backlog", this->_mac), this->_log_messages_backlog); } @@ -747,6 +839,8 @@ void NSPanel::send_websocket_status_update() { {"ip_address", this->_ip_address}, {"rssi", this->_rssi}, {"temperature", this->_temperature}, + {"humidity", this->_humidity}, + {"pressure", this->_pressure}, {"ram_usage", this->_heap_used_pct}, {"update_progress", this->_update_progress}, }; @@ -811,23 +905,49 @@ void NSPanel::send_websocket_status_update() { } bool NSPanel::has_firmware_update() { - auto file_checksum = MqttManagerConfig::get_firmware_checksum(); - if (file_checksum) { - return this->_current_firmware_md5_checksum.compare(*file_checksum) != 0; - } else { - SPDLOG_ERROR("Failed to get checksum for firmware file!"); - return false; + std::string file_checksum = ""; + if (this->_model == MQTT_MANAGER_NSPANEL_MODEL::SONOFF) { + auto result = MqttManagerConfig::get_firmware_sonoff_checksum(); + if (result) { + file_checksum = *result; + } else { + SPDLOG_ERROR("Failed to get checksum for sonoff firmware!"); + return false; + } + } else if (this->_model == MQTT_MANAGER_NSPANEL_MODEL::CUSTOM) { + auto result = MqttManagerConfig::get_firmware_custom_checksum(); + if (result) { + file_checksum = *result; + } else { + SPDLOG_ERROR("Failed to get checksum for custom firmware!"); + return false; + } } + + return this->_current_firmware_md5_checksum.compare(file_checksum) != 0; } bool NSPanel::has_littlefs_update() { - auto file_checksum = MqttManagerConfig::get_littlefs_checksum(); - if (file_checksum) { - return this->_current_littlefs_md5_checksum.compare(*file_checksum) != 0; - } else { - SPDLOG_ERROR("Failed to get checksum for littlefs file!"); - return false; + std::string file_checksum = ""; + if (this->_model == MQTT_MANAGER_NSPANEL_MODEL::SONOFF) { + auto result = MqttManagerConfig::get_littlefs_sonoff_checksum(); + if (result) { + file_checksum = *result; + } else { + SPDLOG_ERROR("Failed to get checksum for sonoff LittleFS!"); + return false; + } + } else if (this->_model == MQTT_MANAGER_NSPANEL_MODEL::CUSTOM) { + auto result = MqttManagerConfig::get_littlefs_custom_checksum(); + if (result) { + file_checksum = *result; + } else { + SPDLOG_ERROR("Failed to get checksum for custom LittleFS!"); + return false; + } } + + return this->_current_littlefs_md5_checksum.compare(file_checksum) != 0; } bool NSPanel::has_tft_update() { @@ -1068,7 +1188,7 @@ bool NSPanel::has_registered_to_manager() { bool NSPanel::register_to_manager(const nlohmann::json ®ister_request_payload) { try { SPDLOG_INFO("Sending registration data to Django for database management."); - std::string url = "http://" MANAGER_ADDRESS ":" MANAGER_PORT "/rest/nspanels"; + std::string url = "http://127.0.0.1:8000/rest/nspanels"; std::string response_data; std::string payload_data = register_request_payload.dump(); @@ -1088,15 +1208,16 @@ bool NSPanel::register_to_manager(const nlohmann::json ®ister_request_payload // Everything was successfull, send registration accept to panel: nlohmann::json response; response["command"] = "register_accept"; - response["address"] = MqttManagerConfig::get_settings().manager_address; - response["port"] = MqttManagerConfig::get_settings().manager_port; + response["address"] = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS); + response["port"] = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_PORT); response["config_topic"] = this->_mqtt_config_topic; std::string reply_topic = fmt::format("nspanel/{}/command", std::string(register_request_payload.at("friendly_name"))); MQTT_Manager::publish(reply_topic, response.dump()); + reply_topic = fmt::format("nspanel/{}/command", this->_mac); + MQTT_Manager::publish(reply_topic, response.dump()); SPDLOG_TRACE("Sending websocket update for NSPanel {}::{} state change.", this->_id, this->_name); nlohmann::json data = nlohmann::json::parse(response_data); - } else { SPDLOG_INFO("NSPanel {}::{} has yet to be accepted. Will not answer request.", this->_id, this->_name); } @@ -1126,7 +1247,7 @@ void NSPanel::register_to_home_assistant() { // Register temperature sensor nlohmann::json temperature_sensor_data = nlohmann::json(base_json); temperature_sensor_data["device_class"] = "temperature"; - if (MqttManagerConfig::get_setting_with_default("use_fahrenheit", "False").compare("True") == 0) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::USE_FAHRENHEIT)) { temperature_sensor_data["unit_of_measurement"] = "°F"; } else { temperature_sensor_data["unit_of_measurement"] = "°C"; @@ -1270,21 +1391,22 @@ void NSPanel::set_relay_state(uint8_t relay, bool state) { void NSPanel::command_callback(NSPanelMQTTManagerCommand &command) { if (command.has_button_pressed()) { if (command.nspanel_id() == this->_id) { - // TODO: Handle button press SPDLOG_DEBUG("NSPanel {}::{} got button {} press,", this->_id, this->_name, command.button_pressed().button_id()); if (command.button_pressed().button_id() == 1) { ButtonMode button_mode = static_cast(this->_settings.button1_mode); switch (button_mode) { case ButtonMode::DETACHED: { - if (this->_settings.button1_detached_mode_light_id.has_value()) { - auto light = EntityManager::get_entity_by_id(MQTT_MANAGER_ENTITY_TYPE::LIGHT, this->_settings.button1_detached_mode_light_id.value()); - if (light) - (*light)->toggle(); - else - SPDLOG_ERROR("Tried to toggle detached light via panel but no light was was found with configured ID."); + if (this->_settings.button1_detached_mode_entity_id.has_value()) { + auto entity = EntityManager::get_entity_by_id(MQTT_MANAGER_ENTITY_TYPE::ANY, this->_settings.button1_detached_mode_entity_id.value()); + if (entity) { + if ((*entity)->can_toggle()) { + (*entity)->toggle(); + } + } else + SPDLOG_ERROR("Tried to toggle detached entity via panel but no entity was found with configured ID '{}'.", this->_settings.button1_detached_mode_entity_id.value()); } else { - SPDLOG_ERROR("Tried to toggle detached light via panel but no light was configured for button."); + SPDLOG_ERROR("Tried to toggle detached entity via panel but no entity was configured for button."); } break; } @@ -1304,14 +1426,16 @@ void NSPanel::command_callback(NSPanelMQTTManagerCommand &command) { ButtonMode button_mode = static_cast(this->_settings.button2_mode); switch (button_mode) { case ButtonMode::DETACHED: { - if (this->_settings.button2_detached_mode_light_id.has_value()) { - auto light = EntityManager::get_entity_by_id(MQTT_MANAGER_ENTITY_TYPE::LIGHT, this->_settings.button2_detached_mode_light_id.value()); - if (light) - (*light)->toggle(); - else - SPDLOG_ERROR("Tried to toggle detached light via panel but no light was was found with configured ID."); + if (this->_settings.button2_detached_mode_entity_id.has_value()) { + auto entity = EntityManager::get_entity_by_id(MQTT_MANAGER_ENTITY_TYPE::ANY, this->_settings.button2_detached_mode_entity_id.value()); + if (entity) { + if ((*entity)->can_toggle()) { + (*entity)->toggle(); + } + } else + SPDLOG_ERROR("Tried to toggle detached entity via panel but no entity was found with configured ID '{}'.", this->_settings.button2_detached_mode_entity_id.value()); } else { - SPDLOG_ERROR("Tried to toggle detached light via panel but no light was configured for button."); + SPDLOG_ERROR("Tried to toggle detached entity via panel but no entity was configured for button."); } break; } diff --git a/docker/MQTTManager/include/nspanel/nspanel.hpp b/docker/MQTTManager/include/nspanel/nspanel.hpp index 2bd1cd80..ac353754 100644 --- a/docker/MQTTManager/include/nspanel/nspanel.hpp +++ b/docker/MQTTManager/include/nspanel/nspanel.hpp @@ -1,10 +1,7 @@ #ifndef MQTT_MANAGER_NSPANEL #define MQTT_MANAGER_NSPANEL -#include "entity/entity.hpp" -#include "protobuf_mqttmanager.pb.h" #include "protobuf_nspanel.pb.h" #include "websocket_server/websocket_server.hpp" -#include #include #include #include @@ -13,7 +10,6 @@ #include #include #include -#include class Room; // Forward declare "Room" as to avoid dependancy loops in CMake @@ -29,6 +25,12 @@ enum MQTT_MANAGER_NSPANEL_STATE { DENIED }; +enum MQTT_MANAGER_NSPANEL_MODEL { + SONOFF, + CUSTOM, + WEB, +}; + struct NSPanelWarningWebsocketRepresentation { std::string level; std::string text; @@ -73,6 +75,7 @@ class NSPanel { std::string get_mac(); std::string get_name(); MQTT_MANAGER_NSPANEL_STATE get_state(); + MQTT_MANAGER_NSPANEL_MODEL get_model(); void mqtt_callback(std::string topic, std::string payload); /** @@ -177,8 +180,9 @@ class NSPanel { uint32_t _id; database_manager::NSPanel _settings; // Settings loaded from database std::mutex _settings_mutex; // Mutex to only allow access to _settings for one thread at the time - std::string _mac; - std::string _name; + std::string _mac; // MAC address of the NSPanel. + MQTT_MANAGER_NSPANEL_MODEL _model; // The model of the NSPanel. + std::string _name; // Friendly name of the NSPanel. bool _is_us_panel; enum US_PANEL_ORIENTATION { LANDSCAPE_LEFT, @@ -190,6 +194,8 @@ class NSPanel { std::string _ip_address; int16_t _rssi; float _temperature; + float _humidity; + float _pressure; uint8_t _heap_used_pct; uint8_t _update_progress; MQTT_MANAGER_NSPANEL_STATE _state; @@ -205,7 +211,9 @@ class NSPanel { DIRECT, DETACHED, MQTT_PAYLOAD, - FOLLOW + FOLLOW, + THERMOSTAT_HEATING, + THERMOSTAT_COOLING, }; // MQTT Stuff: @@ -233,6 +241,10 @@ class NSPanel { std::string _mqtt_status_report_topic; // The topic to send out temperature in raw format instead of encoded in protobuf status report std::string _mqtt_temperature_topic; + // The topic to send out humidity in raw format instead of encoded in protobuf status report + std::string _mqtt_humidity_topic; + // The topic to send out pressure in raw format instead of encoded in protobuf status report + std::string _mqtt_pressure_topic; // The topic to send commands to panel to via MQTT std::string _mqtt_command_topic; // Home Assistant MQTT registration topics: diff --git a/docker/MQTTManager/include/openhab_manager/openhab_manager.cpp b/docker/MQTTManager/include/openhab_manager/openhab_manager.cpp index 3a9e3d05..82d08336 100644 --- a/docker/MQTTManager/include/openhab_manager/openhab_manager.cpp +++ b/docker/MQTTManager/include/openhab_manager/openhab_manager.cpp @@ -75,8 +75,8 @@ void OpenhabManager::reload_config() { bool reconnect = false; { std::lock_guard lock_guard(OpenhabManager::_setting_values_mutex); - std::string address = MqttManagerConfig::get_setting_with_default("openhab_address", ""); - std::string token = MqttManagerConfig::get_setting_with_default("openhab_token", ""); + std::string address = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPENHAB_ADDRESS); + std::string token = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPENHAB_TOKEN); if (OpenhabManager::_openhab_address.compare(address) != 0 || OpenhabManager::_openhab_token.compare(token) != 0) { OpenhabManager::_openhab_address = address; @@ -250,7 +250,11 @@ void OpenhabManager::_send_string(std::string &data) { if (OpenhabManager::_websocket != nullptr && OpenhabManager::_connected) { std::lock_guard mtex_lock(OpenhabManager::_mutex_websocket_write_access); SPDLOG_TRACE("[OH WS] Sending data: {}", data); - OpenhabManager::_websocket->send(data); + try { + OpenhabManager::_websocket->send(data); + } catch (std::exception &e) { + SPDLOG_ERROR("[OH WS] Send failed. Caught error: {}", e.what()); + } } } @@ -282,7 +286,11 @@ void OpenhabManager::_send_keepalive() { keepalive_message["topic"] = "openhab/websocket/heartbeat"; keepalive_message["payload"] = "PING"; keepalive_message["source"] = "NSPanelManager::MqttManager"; - OpenhabManager::_websocket->send(keepalive_message.dump()); + try { + OpenhabManager::_websocket->send(keepalive_message.dump()); + } catch (std::exception &e) { + SPDLOG_ERROR("[Openhab WS] Keepalive send failed. Caught error: {}", e.what()); + } } } diff --git a/docker/MQTTManager/include/openhab_manager/openhab_manager.hpp b/docker/MQTTManager/include/openhab_manager/openhab_manager.hpp index 57d48203..59231954 100644 --- a/docker/MQTTManager/include/openhab_manager/openhab_manager.hpp +++ b/docker/MQTTManager/include/openhab_manager/openhab_manager.hpp @@ -37,13 +37,25 @@ class OpenhabManager { OpenhabManager::_openhab_item_observers[item].connect(callback); std::string data; + data = OpenhabManager::_fetch_item_state_via_rest(item); + if (data.empty()) { + SPDLOG_WARN("Failed to fetch item state for '{}'. Will not be able to update items current state.", item); + return; + } + + nlohmann::json update_data; + update_data["type"] = "ItemStateFetched"; + try { + update_data["payload"] = nlohmann::json::parse(data); + } catch (std::exception &e) { + SPDLOG_ERROR("Failed to parse JSON payload from OpenHAB REST API. Will not propegate status. Diagnostic information: {}", boost::diagnostic_information(e, true)); + SPDLOG_ERROR("Stacktrace: {}", boost::stacktrace::to_string(boost::stacktrace::stacktrace())); + SPDLOG_ERROR("Payload data: {}", data); + return; + } try { - data = OpenhabManager::_fetch_item_state_via_rest(item); if (data.length() > 0) { SPDLOG_TRACE("Creating ItemStateFetched event."); - nlohmann::json update_data; - update_data["type"] = "ItemStateFetched"; - update_data["payload"] = nlohmann::json::parse(data); SPDLOG_TRACE("ItemStateFetched event created. Updating observer for item {}.", item); OpenhabManager::_openhab_item_observers[item](update_data); } else { diff --git a/docker/MQTTManager/include/protobuf/protobuf_nspanel.pb.cc b/docker/MQTTManager/include/protobuf/protobuf_nspanel.pb.cc index 9dd31922..7b7a8045 100644 --- a/docker/MQTTManager/include/protobuf/protobuf_nspanel.pb.cc +++ b/docker/MQTTManager/include/protobuf/protobuf_nspanel.pb.cc @@ -164,6 +164,51 @@ struct NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPageDefaultTypeInternal PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPageDefaultTypeInternal _NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPage_default_instance_; +inline constexpr NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::Impl_::Impl_( + ::_pbi::ConstantInitialized) noexcept + : thermostat_id_{0}, + temperature_{0}, + _cached_size_{0} {} + +template +PROTOBUF_CONSTEXPR NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand(::_pbi::ConstantInitialized) + : _impl_(::_pbi::ConstantInitialized()) {} +struct NSPanelMQTTManagerCommand_ThermostatTemperatureCommandDefaultTypeInternal { + PROTOBUF_CONSTEXPR NSPanelMQTTManagerCommand_ThermostatTemperatureCommandDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {} + ~NSPanelMQTTManagerCommand_ThermostatTemperatureCommandDefaultTypeInternal() {} + union { + NSPanelMQTTManagerCommand_ThermostatTemperatureCommand _instance; + }; +}; + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 NSPanelMQTTManagerCommand_ThermostatTemperatureCommandDefaultTypeInternal _NSPanelMQTTManagerCommand_ThermostatTemperatureCommand_default_instance_; + +inline constexpr NSPanelMQTTManagerCommand_ThermostatCommand::Impl_::Impl_( + ::_pbi::ConstantInitialized) noexcept + : option_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + new_value_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + thermostat_id_{0}, + _cached_size_{0} {} + +template +PROTOBUF_CONSTEXPR NSPanelMQTTManagerCommand_ThermostatCommand::NSPanelMQTTManagerCommand_ThermostatCommand(::_pbi::ConstantInitialized) + : _impl_(::_pbi::ConstantInitialized()) {} +struct NSPanelMQTTManagerCommand_ThermostatCommandDefaultTypeInternal { + PROTOBUF_CONSTEXPR NSPanelMQTTManagerCommand_ThermostatCommandDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {} + ~NSPanelMQTTManagerCommand_ThermostatCommandDefaultTypeInternal() {} + union { + NSPanelMQTTManagerCommand_ThermostatCommand _instance; + }; +}; + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 NSPanelMQTTManagerCommand_ThermostatCommandDefaultTypeInternal _NSPanelMQTTManagerCommand_ThermostatCommand_default_instance_; + inline constexpr NSPanelMQTTManagerCommand_SaveSceneCommand::Impl_::Impl_( ::_pbi::ConstantInitialized) noexcept : entity_page_id_{0}, @@ -392,6 +437,10 @@ inline constexpr NSPanelStatusReport::Impl_::Impl_( rssi_{0}, heap_used_pct_{0}, temperature_{0}, + humidity_{0}, + has_humidity_{false}, + has_pressure_{false}, + pressure_{0}, _cached_size_{0} {} template @@ -494,6 +543,10 @@ inline constexpr NSPanelConfig::Impl_::Impl_( optimistic_mode_{false}, locked_to_default_room_{false}, default_light_brightess_{0}, + button1_lower_temperature_{0}, + button1_upper_temperature_{0}, + button2_lower_temperature_{0}, + button2_upper_temperature_{0}, _cached_size_{0} {} template @@ -566,6 +619,10 @@ const ::uint32_t PROTOBUF_FIELD_OFFSET(::NSPanelConfig, _impl_.default_light_brightess_), PROTOBUF_FIELD_OFFSET(::NSPanelConfig, _impl_.locked_to_default_room_), PROTOBUF_FIELD_OFFSET(::NSPanelConfig, _impl_.inside_temperature_sensor_mqtt_topic_), + PROTOBUF_FIELD_OFFSET(::NSPanelConfig, _impl_.button1_lower_temperature_), + PROTOBUF_FIELD_OFFSET(::NSPanelConfig, _impl_.button1_upper_temperature_), + PROTOBUF_FIELD_OFFSET(::NSPanelConfig, _impl_.button2_lower_temperature_), + PROTOBUF_FIELD_OFFSET(::NSPanelConfig, _impl_.button2_upper_temperature_), ~0u, // no _has_bits_ PROTOBUF_FIELD_OFFSET(::NSPanelWarning, _internal_metadata_), ~0u, // no _extensions_ @@ -595,6 +652,10 @@ const ::uint32_t PROTOBUF_FIELD_OFFSET(::NSPanelStatusReport, _impl_.md5_firmware_), PROTOBUF_FIELD_OFFSET(::NSPanelStatusReport, _impl_.md5_littlefs_), PROTOBUF_FIELD_OFFSET(::NSPanelStatusReport, _impl_.md5_tft_gui_), + PROTOBUF_FIELD_OFFSET(::NSPanelStatusReport, _impl_.has_humidity_), + PROTOBUF_FIELD_OFFSET(::NSPanelStatusReport, _impl_.humidity_), + PROTOBUF_FIELD_OFFSET(::NSPanelStatusReport, _impl_.has_pressure_), + PROTOBUF_FIELD_OFFSET(::NSPanelStatusReport, _impl_.pressure_), ~0u, // no _has_bits_ PROTOBUF_FIELD_OFFSET(::NSPanelLightStatus, _internal_metadata_), ~0u, // no _extensions_ @@ -762,6 +823,27 @@ const ::uint32_t ~0u, // no sizeof(Split) PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand_ButtonPressed, _impl_.button_id_), ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + ~0u, // no _split_ + ~0u, // no sizeof(Split) + PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand, _impl_.thermostat_id_), + PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand, _impl_.temperature_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand_ThermostatCommand, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + ~0u, // no _split_ + ~0u, // no sizeof(Split) + PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand_ThermostatCommand, _impl_.thermostat_id_), + PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand_ThermostatCommand, _impl_.option_), + PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand_ThermostatCommand, _impl_.new_value_), + ~0u, // no _has_bits_ PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand, _internal_metadata_), ~0u, // no _extensions_ PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand, _impl_._oneof_case_[0]), @@ -775,6 +857,8 @@ const ::uint32_t ::_pbi::kInvalidFieldOffsetTag, ::_pbi::kInvalidFieldOffsetTag, ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand, _impl_.nspanel_id_), PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand, _impl_.CommandData_), }; @@ -783,21 +867,23 @@ static const ::_pbi::MigrationSchema schemas[] ABSL_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = { {0, -1, -1, sizeof(::NSPanelConfig_RoomInfo)}, {11, -1, -1, sizeof(::NSPanelConfig)}, - {51, -1, -1, sizeof(::NSPanelWarning)}, - {61, -1, -1, sizeof(::NSPanelStatusReport)}, - {80, -1, -1, sizeof(::NSPanelLightStatus)}, - {98, -1, -1, sizeof(::NSPanelRoomEntitiesPage_EntitySlot)}, - {113, -1, -1, sizeof(::NSPanelRoomEntitiesPage)}, - {125, -1, -1, sizeof(::NSPanelRoomStatus)}, - {146, -1, -1, sizeof(::NSPanelWeatherUpdate_ForecastItem)}, - {159, -1, -1, sizeof(::NSPanelWeatherUpdate)}, - {175, -1, -1, sizeof(::NSPanelMQTTManagerCommand_FirstPageTurnLightOn)}, - {190, -1, -1, sizeof(::NSPanelMQTTManagerCommand_FirstPageTurnLightOff)}, - {200, -1, -1, sizeof(::NSPanelMQTTManagerCommand_LightCommand)}, - {217, -1, -1, sizeof(::NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPage)}, - {227, -1, -1, sizeof(::NSPanelMQTTManagerCommand_SaveSceneCommand)}, - {237, -1, -1, sizeof(::NSPanelMQTTManagerCommand_ButtonPressed)}, - {246, -1, -1, sizeof(::NSPanelMQTTManagerCommand)}, + {55, -1, -1, sizeof(::NSPanelWarning)}, + {65, -1, -1, sizeof(::NSPanelStatusReport)}, + {88, -1, -1, sizeof(::NSPanelLightStatus)}, + {106, -1, -1, sizeof(::NSPanelRoomEntitiesPage_EntitySlot)}, + {121, -1, -1, sizeof(::NSPanelRoomEntitiesPage)}, + {133, -1, -1, sizeof(::NSPanelRoomStatus)}, + {154, -1, -1, sizeof(::NSPanelWeatherUpdate_ForecastItem)}, + {167, -1, -1, sizeof(::NSPanelWeatherUpdate)}, + {183, -1, -1, sizeof(::NSPanelMQTTManagerCommand_FirstPageTurnLightOn)}, + {198, -1, -1, sizeof(::NSPanelMQTTManagerCommand_FirstPageTurnLightOff)}, + {208, -1, -1, sizeof(::NSPanelMQTTManagerCommand_LightCommand)}, + {225, -1, -1, sizeof(::NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPage)}, + {235, -1, -1, sizeof(::NSPanelMQTTManagerCommand_SaveSceneCommand)}, + {245, -1, -1, sizeof(::NSPanelMQTTManagerCommand_ButtonPressed)}, + {254, -1, -1, sizeof(::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand)}, + {264, -1, -1, sizeof(::NSPanelMQTTManagerCommand_ThermostatCommand)}, + {275, -1, -1, sizeof(::NSPanelMQTTManagerCommand)}, }; static const ::_pb::Message* const file_default_instances[] = { &::_NSPanelConfig_RoomInfo_default_instance_._instance, @@ -816,11 +902,13 @@ static const ::_pb::Message* const file_default_instances[] = { &::_NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPage_default_instance_._instance, &::_NSPanelMQTTManagerCommand_SaveSceneCommand_default_instance_._instance, &::_NSPanelMQTTManagerCommand_ButtonPressed_default_instance_._instance, + &::_NSPanelMQTTManagerCommand_ThermostatTemperatureCommand_default_instance_._instance, + &::_NSPanelMQTTManagerCommand_ThermostatCommand_default_instance_._instance, &::_NSPanelMQTTManagerCommand_default_instance_._instance, }; const char descriptor_table_protodef_protobuf_5fnspanel_2eproto[] ABSL_ATTRIBUTE_SECTION_VARIABLE( protodesc_cold) = { - "\n\026protobuf_nspanel.proto\"\330\013\n\rNSPanelConf" + "\n\026protobuf_nspanel.proto\"\216\r\n\rNSPanelConf" "ig\022\014\n\004name\030\001 \001(\t\022\024\n\014default_room\030\002 \001(\005\0227" "\n\014default_page\030\003 \001(\0162!.NSPanelConfig.NSP" "anelDefaultPage\022&\n\036screensaver_activatio" @@ -848,111 +936,125 @@ const char descriptor_table_protodef_protobuf_5fnspanel_2eproto[] ABSL_ATTRIBUTE "_group\030# \003(\005\022\032\n\022relay2_relay_group\030% \003(\005" "\022\037\n\027default_light_brightess\030& \001(\005\022\036\n\026loc" "ked_to_default_room\030\' \001(\010\022,\n$inside_temp" - "erature_sensor_mqtt_topic\030( \001(\t\032L\n\010RoomI" - "nfo\022\017\n\007room_id\030\001 \001(\005\022\027\n\017entity_page_ids\030" - "\002 \003(\005\022\026\n\016scene_page_ids\030\003 \003(\005\"8\n\022NSPanel" - "DefaultPage\022\010\n\004HOME\020\000\022\n\n\006SCENES\020\001\022\014\n\010ENT" - "ITIES\020\002\"\250\001\n\026NSPanelScreensaverMode\022\033\n\027WE" - "ATHER_WITH_BACKGROUND\020\000\022\036\n\032WEATHER_WITHO" - "UT_BACKGROUND\020\001\022\034\n\030DATETIME_WITH_BACKGRO" - "UND\020\003\022\037\n\033DATETIME_WITHOUT_BACKGROUND\020\004\022\022" - "\n\016NO_SCREENSAVER\020\005\"\?\n\021NSPanelButtonMode\022" - "\n\n\006DIRECT\020\000\022\n\n\006FOLLOW\020\001\022\022\n\016NOTIFY_MANAGE" - "R\020\002\"C\n\016NSPanelWarning\022#\n\005level\030\001 \001(\0162\024.N" - "SPanelWarningLevel\022\014\n\004text\030\002 \001(\t\"\212\003\n\023NSP" - "anelStatusReport\0221\n\rnspanel_state\030\001 \001(\0162" - "\032.NSPanelStatusReport.state\022\027\n\017update_pr" - "ogress\030\002 \001(\005\022\014\n\004rssi\030\003 \001(\005\022\025\n\rheap_used_" - "pct\030\004 \001(\005\022\023\n\013mac_address\030\005 \001(\t\022\023\n\013temper" - "ature\030\006 \001(\002\022\022\n\nip_address\030\007 \001(\t\022!\n\010warni" - "ngs\030\010 \003(\0132\017.NSPanelWarning\022\024\n\014md5_firmwa" - "re\030\t \001(\t\022\024\n\014md5_littlefs\030\n \001(\t\022\023\n\013md5_tf" - "t_gui\030\013 \001(\t\"`\n\005state\022\n\n\006ONLINE\020\000\022\013\n\007OFFL" - "INE\020\001\022\020\n\014UPDATING_TFT\020\002\022\025\n\021UPDATING_FIRM" - "WARE\020\003\022\025\n\021UPDATING_LITTLEFS\020\004\"\325\001\n\022NSPane" - "lLightStatus\022\n\n\002id\030\001 \001(\005\022\014\n\004name\030\002 \001(\t\022\017" - "\n\007can_dim\030\003 \001(\010\022\035\n\025can_color_temperature" - "\030\004 \001(\010\022\017\n\007can_rgb\030\005 \001(\010\022\023\n\013light_level\030\006" - " \001(\005\022\022\n\ncolor_temp\030\007 \001(\005\022\013\n\003hue\030\010 \001(\005\022\022\n" - "\nsaturation\030\t \001(\005\022\032\n\022room_view_position\030" - "\n \001(\005\"\230\002\n\027NSPanelRoomEntitiesPage\022\n\n\002id\030" - "\001 \001(\005\022\021\n\tpage_type\030\002 \001(\005\022\023\n\013header_text\030" - "\003 \001(\t\0225\n\010entities\030\004 \003(\0132#.NSPanelRoomEnt" - "itiesPage.EntitySlot\032\221\001\n\nEntitySlot\022\032\n\022r" - "oom_view_position\030\001 \001(\005\022\014\n\004name\030\002 \001(\t\022\014\n" - "\004icon\030\003 \001(\t\022\013\n\003pco\030\004 \001(\005\022\014\n\004pco2\030\005 \001(\005\022\026" - "\n\016can_save_scene\030\006 \001(\010\022\030\n\020mqtt_state_top" - "ic\030\007 \001(\t\"\226\003\n\021NSPanelRoomStatus\022\n\n\002id\030\001 \001" - "(\005\022\014\n\004name\030\002 \001(\t\022\031\n\021average_dim_level\030\003 " - "\001(\005\022 \n\030ceiling_lights_dim_level\030\004 \001(\005\022\036\n" - "\026table_lights_dim_level\030\005 \001(\005\022!\n\031average" - "_color_temperature\030\006 \001(\005\022.\n&ceiling_ligh" - "ts_color_temperature_value\030\007 \001(\005\022,\n$tabl" - "e_lights_color_temperature_value\030\010 \001(\005\022\032" - "\n\022num_ceiling_lights\030\t \001(\005\022\030\n\020num_table_" - "lights\030\n \001(\005\022\035\n\025num_ceiling_lights_on\030\013 " - "\001(\005\022\033\n\023num_table_lights_on\030\014 \001(\005\022\027\n\017enti" - "ty_page_ids\030\r \003(\005\"\277\003\n\024NSPanelWeatherUpda" - "te\022:\n\016forecast_items\030\001 \003(\0132\".NSPanelWeat" - "herUpdate.ForecastItem\022\034\n\024current_weathe" - "r_icon\030\002 \001(\t\022\"\n\032current_temperature_stri" - "ng\030\003 \001(\t\022\"\n\032current_maxmin_temperature\030\004" - " \001(\t\022\033\n\023current_wind_string\030\005 \001(\t\022\026\n\016sun" - "rise_string\030\006 \001(\t\022\025\n\rsunset_string\030\007 \001(\t" - "\022$\n\034current_precipitation_string\030\010 \001(\t\032\222" - "\001\n\014ForecastItem\022\024\n\014weather_icon\030\001 \001(\t\022\034\n" - "\024precipitation_string\030\002 \001(\t\022!\n\031temperatu" - "re_maxmin_string\030\003 \001(\t\022\023\n\013wind_string\030\004 " - "\001(\t\022\026\n\016display_string\030\005 \001(\t\"\315\n\n\031NSPanelM" - "QTTManagerCommand\022M\n\022first_page_turn_on\030" - "\001 \001(\0132/.NSPanelMQTTManagerCommand.FirstP" - "ageTurnLightOnH\000\022O\n\023first_page_turn_off\030" - "\002 \001(\01320.NSPanelMQTTManagerCommand.FirstP" - "ageTurnLightOffH\000\022@\n\rlight_command\030\003 \001(\013" - "2\'.NSPanelMQTTManagerCommand.LightComman" - "dH\000\022c\n toggle_entity_from_entities_page\030" - "\004 \001(\01327.NSPanelMQTTManagerCommand.Toggle" - "EntityFromEntitiesPageH\000\022I\n\022save_scene_c" - "ommand\030\005 \001(\0132+.NSPanelMQTTManagerCommand" - ".SaveSceneCommandH\000\022B\n\016button_pressed\030\006 " - "\001(\0132(.NSPanelMQTTManagerCommand.ButtonPr" - "essedH\000\022\022\n\nnspanel_id\030d \001(\005\032\372\001\n\024FirstPag" - "eTurnLightOn\022E\n\raffect_lights\030\001 \001(\0162..NS" - "PanelMQTTManagerCommand.AffectLightsOpti" - "ons\022\037\n\027brightness_slider_value\030\002 \001(\005\022\033\n\023" - "kelvin_slider_value\030\003 \001(\005\022\025\n\rselected_ro" - "om\030\004 \001(\005\022\016\n\006global\030\005 \001(\010\022\034\n\024has_brightne" - "ss_value\030\006 \001(\010\022\030\n\020has_kelvin_value\030\007 \001(\010" - "\032n\n\025FirstPageTurnLightOff\022E\n\raffect_ligh" - "ts\030\001 \001(\0162..NSPanelMQTTManagerCommand.Aff" - "ectLightsOptions\022\016\n\006global\030\002 \001(\010\032\321\001\n\014Lig" - "htCommand\022\021\n\tlight_ids\030\001 \003(\005\022\026\n\016has_brig" - "htness\030\002 \001(\010\022\022\n\nbrightness\030\003 \001(\005\022\035\n\025has_" - "color_temperature\030\004 \001(\010\022\031\n\021color_tempera" - "ture\030\005 \001(\005\022\017\n\007has_hue\030\006 \001(\010\022\013\n\003hue\030\007 \001(\005" - "\022\026\n\016has_saturation\030\010 \001(\010\022\022\n\nsaturation\030\t" - " \001(\005\032K\n\034ToggleEntityFromEntitiesPage\022\026\n\016" - "entity_page_id\030\001 \001(\005\022\023\n\013entity_slot\030\002 \001(" - "\005\032\?\n\020SaveSceneCommand\022\026\n\016entity_page_id\030" - "\001 \001(\005\022\023\n\013entity_slot\030\002 \001(\005\032\"\n\rButtonPres" - "sed\022\021\n\tbutton_id\030\002 \001(\005\"D\n\023AffectLightsOp" - "tions\022\007\n\003ALL\020\000\022\020\n\014TABLE_LIGHTS\020\001\022\022\n\016CEIL" - "ING_LIGHTS\020\002B\r\n\013CommandData*[\n\023NSPanelWa" - "rningLevel\022\014\n\010CRITICAL\020\000\022\t\n\005ERROR\020\001\022\013\n\007W" - "ARNING\020\002\022\010\n\004INFO\020\003\022\t\n\005DEBUG\020\004\022\t\n\005TRACE\020\005" - "b\006proto3" + "erature_sensor_mqtt_topic\030( \001(\t\022!\n\031butto" + "n1_lower_temperature\030) \001(\005\022!\n\031button1_up" + "per_temperature\030* \001(\005\022!\n\031button2_lower_t" + "emperature\030+ \001(\005\022!\n\031button2_upper_temper" + "ature\030, \001(\005\032L\n\010RoomInfo\022\017\n\007room_id\030\001 \001(\005" + "\022\027\n\017entity_page_ids\030\002 \003(\005\022\026\n\016scene_page_" + "ids\030\003 \003(\005\"8\n\022NSPanelDefaultPage\022\010\n\004HOME\020" + "\000\022\n\n\006SCENES\020\001\022\014\n\010ENTITIES\020\002\"\250\001\n\026NSPanelS" + "creensaverMode\022\033\n\027WEATHER_WITH_BACKGROUN" + "D\020\000\022\036\n\032WEATHER_WITHOUT_BACKGROUND\020\001\022\034\n\030D" + "ATETIME_WITH_BACKGROUND\020\003\022\037\n\033DATETIME_WI" + "THOUT_BACKGROUND\020\004\022\022\n\016NO_SCREENSAVER\020\005\"i" + "\n\021NSPanelButtonMode\022\n\n\006DIRECT\020\000\022\n\n\006FOLLO" + "W\020\001\022\022\n\016NOTIFY_MANAGER\020\002\022\023\n\017THERMOSTAT_HE" + "AT\020\003\022\023\n\017THERMOSTAT_COOL\020\004\"C\n\016NSPanelWarn" + "ing\022#\n\005level\030\001 \001(\0162\024.NSPanelWarningLevel" + "\022\014\n\004text\030\002 \001(\t\"\332\003\n\023NSPanelStatusReport\0221" + "\n\rnspanel_state\030\001 \001(\0162\032.NSPanelStatusRep" + "ort.state\022\027\n\017update_progress\030\002 \001(\005\022\014\n\004rs" + "si\030\003 \001(\005\022\025\n\rheap_used_pct\030\004 \001(\005\022\023\n\013mac_a" + "ddress\030\005 \001(\t\022\023\n\013temperature\030\006 \001(\002\022\022\n\nip_" + "address\030\007 \001(\t\022!\n\010warnings\030\010 \003(\0132\017.NSPane" + "lWarning\022\024\n\014md5_firmware\030\t \001(\t\022\024\n\014md5_li" + "ttlefs\030\n \001(\t\022\023\n\013md5_tft_gui\030\013 \001(\t\022\024\n\014has" + "_humidity\030\014 \001(\010\022\020\n\010humidity\030\r \001(\002\022\024\n\014has" + "_pressure\030\016 \001(\010\022\020\n\010pressure\030\017 \001(\002\"`\n\005sta" + "te\022\n\n\006ONLINE\020\000\022\013\n\007OFFLINE\020\001\022\020\n\014UPDATING_" + "TFT\020\002\022\025\n\021UPDATING_FIRMWARE\020\003\022\025\n\021UPDATING" + "_LITTLEFS\020\004\"\325\001\n\022NSPanelLightStatus\022\n\n\002id" + "\030\001 \001(\005\022\014\n\004name\030\002 \001(\t\022\017\n\007can_dim\030\003 \001(\010\022\035\n" + "\025can_color_temperature\030\004 \001(\010\022\017\n\007can_rgb\030" + "\005 \001(\010\022\023\n\013light_level\030\006 \001(\005\022\022\n\ncolor_temp" + "\030\007 \001(\005\022\013\n\003hue\030\010 \001(\005\022\022\n\nsaturation\030\t \001(\005\022" + "\032\n\022room_view_position\030\n \001(\005\"\230\002\n\027NSPanelR" + "oomEntitiesPage\022\n\n\002id\030\001 \001(\005\022\021\n\tpage_type" + "\030\002 \001(\005\022\023\n\013header_text\030\003 \001(\t\0225\n\010entities\030" + "\004 \003(\0132#.NSPanelRoomEntitiesPage.EntitySl" + "ot\032\221\001\n\nEntitySlot\022\032\n\022room_view_position\030" + "\001 \001(\005\022\014\n\004name\030\002 \001(\t\022\014\n\004icon\030\003 \001(\t\022\013\n\003pco" + "\030\004 \001(\005\022\014\n\004pco2\030\005 \001(\005\022\026\n\016can_save_scene\030\006" + " \001(\010\022\030\n\020mqtt_state_topic\030\007 \001(\t\"\226\003\n\021NSPan" + "elRoomStatus\022\n\n\002id\030\001 \001(\005\022\014\n\004name\030\002 \001(\t\022\031" + "\n\021average_dim_level\030\003 \001(\005\022 \n\030ceiling_lig" + "hts_dim_level\030\004 \001(\005\022\036\n\026table_lights_dim_" + "level\030\005 \001(\005\022!\n\031average_color_temperature" + "\030\006 \001(\005\022.\n&ceiling_lights_color_temperatu" + "re_value\030\007 \001(\005\022,\n$table_lights_color_tem" + "perature_value\030\010 \001(\005\022\032\n\022num_ceiling_ligh" + "ts\030\t \001(\005\022\030\n\020num_table_lights\030\n \001(\005\022\035\n\025nu" + "m_ceiling_lights_on\030\013 \001(\005\022\033\n\023num_table_l" + "ights_on\030\014 \001(\005\022\027\n\017entity_page_ids\030\r \003(\005\"" + "\277\003\n\024NSPanelWeatherUpdate\022:\n\016forecast_ite" + "ms\030\001 \003(\0132\".NSPanelWeatherUpdate.Forecast" + "Item\022\034\n\024current_weather_icon\030\002 \001(\t\022\"\n\032cu" + "rrent_temperature_string\030\003 \001(\t\022\"\n\032curren" + "t_maxmin_temperature\030\004 \001(\t\022\033\n\023current_wi" + "nd_string\030\005 \001(\t\022\026\n\016sunrise_string\030\006 \001(\t\022" + "\025\n\rsunset_string\030\007 \001(\t\022$\n\034current_precip" + "itation_string\030\010 \001(\t\032\222\001\n\014ForecastItem\022\024\n" + "\014weather_icon\030\001 \001(\t\022\034\n\024precipitation_str" + "ing\030\002 \001(\t\022!\n\031temperature_maxmin_string\030\003" + " \001(\t\022\023\n\013wind_string\030\004 \001(\t\022\026\n\016display_str" + "ing\030\005 \001(\t\"\227\r\n\031NSPanelMQTTManagerCommand\022" + "M\n\022first_page_turn_on\030\001 \001(\0132/.NSPanelMQT" + "TManagerCommand.FirstPageTurnLightOnH\000\022O" + "\n\023first_page_turn_off\030\002 \001(\01320.NSPanelMQT" + "TManagerCommand.FirstPageTurnLightOffH\000\022" + "@\n\rlight_command\030\003 \001(\0132\'.NSPanelMQTTMana" + "gerCommand.LightCommandH\000\022c\n toggle_enti" + "ty_from_entities_page\030\004 \001(\01327.NSPanelMQT" + "TManagerCommand.ToggleEntityFromEntities" + "PageH\000\022I\n\022save_scene_command\030\005 \001(\0132+.NSP" + "anelMQTTManagerCommand.SaveSceneCommandH" + "\000\022B\n\016button_pressed\030\006 \001(\0132(.NSPanelMQTTM" + "anagerCommand.ButtonPressedH\000\022a\n\036thermos" + "tat_temperature_command\030\007 \001(\01327.NSPanelM" + "QTTManagerCommand.ThermostatTemperatureC" + "ommandH\000\022J\n\022thermostat_command\030\010 \001(\0132,.N" + "SPanelMQTTManagerCommand.ThermostatComma" + "ndH\000\022\022\n\nnspanel_id\030d \001(\005\032\372\001\n\024FirstPageTu" + "rnLightOn\022E\n\raffect_lights\030\001 \001(\0162..NSPan" + "elMQTTManagerCommand.AffectLightsOptions" + "\022\037\n\027brightness_slider_value\030\002 \001(\005\022\033\n\023kel" + "vin_slider_value\030\003 \001(\005\022\025\n\rselected_room\030" + "\004 \001(\005\022\016\n\006global\030\005 \001(\010\022\034\n\024has_brightness_" + "value\030\006 \001(\010\022\030\n\020has_kelvin_value\030\007 \001(\010\032n\n" + "\025FirstPageTurnLightOff\022E\n\raffect_lights\030" + "\001 \001(\0162..NSPanelMQTTManagerCommand.Affect" + "LightsOptions\022\016\n\006global\030\002 \001(\010\032\321\001\n\014LightC" + "ommand\022\021\n\tlight_ids\030\001 \003(\005\022\026\n\016has_brightn" + "ess\030\002 \001(\010\022\022\n\nbrightness\030\003 \001(\005\022\035\n\025has_col" + "or_temperature\030\004 \001(\010\022\031\n\021color_temperatur" + "e\030\005 \001(\005\022\017\n\007has_hue\030\006 \001(\010\022\013\n\003hue\030\007 \001(\005\022\026\n" + "\016has_saturation\030\010 \001(\010\022\022\n\nsaturation\030\t \001(" + "\005\032K\n\034ToggleEntityFromEntitiesPage\022\026\n\016ent" + "ity_page_id\030\001 \001(\005\022\023\n\013entity_slot\030\002 \001(\005\032\?" + "\n\020SaveSceneCommand\022\026\n\016entity_page_id\030\001 \001" + "(\005\022\023\n\013entity_slot\030\002 \001(\005\032\"\n\rButtonPressed" + "\022\021\n\tbutton_id\030\002 \001(\005\032J\n\034ThermostatTempera" + "tureCommand\022\025\n\rthermostat_id\030\001 \001(\005\022\023\n\013te" + "mperature\030\002 \001(\002\032M\n\021ThermostatCommand\022\025\n\r" + "thermostat_id\030\001 \001(\005\022\016\n\006option\030\002 \001(\t\022\021\n\tn" + "ew_value\030\003 \001(\t\"D\n\023AffectLightsOptions\022\007\n" + "\003ALL\020\000\022\020\n\014TABLE_LIGHTS\020\001\022\022\n\016CEILING_LIGH" + "TS\020\002B\r\n\013CommandData*[\n\023NSPanelWarningLev" + "el\022\014\n\010CRITICAL\020\000\022\t\n\005ERROR\020\001\022\013\n\007WARNING\020\002" + "\022\010\n\004INFO\020\003\022\t\n\005DEBUG\020\004\022\t\n\005TRACE\020\005b\006proto3" }; static ::absl::once_flag descriptor_table_protobuf_5fnspanel_2eproto_once; PROTOBUF_CONSTINIT const ::_pbi::DescriptorTable descriptor_table_protobuf_5fnspanel_2eproto = { false, false, - 4808, + 5400, descriptor_table_protodef_protobuf_5fnspanel_2eproto, "protobuf_nspanel.proto", &descriptor_table_protobuf_5fnspanel_2eproto_once, nullptr, 0, - 17, + 19, schemas, file_default_instances, TableStruct_protobuf_5fnspanel_2eproto::offsets, @@ -1008,9 +1110,9 @@ const ::google::protobuf::EnumDescriptor* NSPanelConfig_NSPanelButtonMode_descri return file_level_enum_descriptors_protobuf_5fnspanel_2eproto[2]; } PROTOBUF_CONSTINIT const uint32_t NSPanelConfig_NSPanelButtonMode_internal_data_[] = { - 196608u, 0u, }; + 327680u, 0u, }; bool NSPanelConfig_NSPanelButtonMode_IsValid(int value) { - return 0 <= value && value <= 2; + return 0 <= value && value <= 4; } #if (__cplusplus < 201703) && \ (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) @@ -1018,6 +1120,8 @@ bool NSPanelConfig_NSPanelButtonMode_IsValid(int value) { constexpr NSPanelConfig_NSPanelButtonMode NSPanelConfig::DIRECT; constexpr NSPanelConfig_NSPanelButtonMode NSPanelConfig::FOLLOW; constexpr NSPanelConfig_NSPanelButtonMode NSPanelConfig::NOTIFY_MANAGER; +constexpr NSPanelConfig_NSPanelButtonMode NSPanelConfig::THERMOSTAT_HEAT; +constexpr NSPanelConfig_NSPanelButtonMode NSPanelConfig::THERMOSTAT_COOL; constexpr NSPanelConfig_NSPanelButtonMode NSPanelConfig::NSPanelButtonMode_MIN; constexpr NSPanelConfig_NSPanelButtonMode NSPanelConfig::NSPanelButtonMode_MAX; constexpr int NSPanelConfig::NSPanelButtonMode_ARRAYSIZE; @@ -1372,9 +1476,9 @@ NSPanelConfig::NSPanelConfig( offsetof(Impl_, default_room_), reinterpret_cast(&from._impl_) + offsetof(Impl_, default_room_), - offsetof(Impl_, default_light_brightess_) - + offsetof(Impl_, button2_upper_temperature_) - offsetof(Impl_, default_room_) + - sizeof(Impl_::default_light_brightess_)); + sizeof(Impl_::button2_upper_temperature_)); // @@protoc_insertion_point(copy_constructor:NSPanelConfig) } @@ -1397,9 +1501,9 @@ inline void NSPanelConfig::SharedCtor(::_pb::Arena* arena) { ::memset(reinterpret_cast(&_impl_) + offsetof(Impl_, default_room_), 0, - offsetof(Impl_, default_light_brightess_) - + offsetof(Impl_, button2_upper_temperature_) - offsetof(Impl_, default_room_) + - sizeof(Impl_::default_light_brightess_)); + sizeof(Impl_::button2_upper_temperature_)); } NSPanelConfig::~NSPanelConfig() { // @@protoc_insertion_point(destructor:NSPanelConfig) @@ -1434,15 +1538,15 @@ NSPanelConfig::GetClassData() const { return _data_.base(); } PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 -const ::_pbi::TcParseTable<5, 32, 1, 94, 7> NSPanelConfig::_table_ = { +const ::_pbi::TcParseTable<5, 36, 1, 94, 7> NSPanelConfig::_table_ = { { 0, // no _has_bits_ 0, // no _extensions_ - 40, 248, // max_field_number, fast_idx_mask + 44, 248, // max_field_number, fast_idx_mask offsetof(decltype(_table_), field_lookup_table), 515899392, // skipmap offsetof(decltype(_table_), field_entries), - 32, // num_field_entries + 36, // num_field_entries 1, // num_aux_entries offsetof(decltype(_table_), aux_entries), &_NSPanelConfig_default_instance_._instance, @@ -1528,9 +1632,15 @@ const ::_pbi::TcParseTable<5, 32, 1, 94, 7> NSPanelConfig::_table_ = { // .NSPanelConfig.NSPanelButtonMode button2_mode = 25; {::_pbi::TcParser::FastV32S2, {456, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.button2_mode_)}}, - {::_pbi::TcParser::MiniParse, {}}, - {::_pbi::TcParser::MiniParse, {}}, - {::_pbi::TcParser::MiniParse, {}}, + // int32 button1_upper_temperature = 42; + {::_pbi::TcParser::FastV32S2, + {720, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.button1_upper_temperature_)}}, + // int32 button2_lower_temperature = 43; + {::_pbi::TcParser::FastV32S2, + {728, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.button2_lower_temperature_)}}, + // int32 button2_upper_temperature = 44; + {::_pbi::TcParser::FastV32S2, + {736, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.button2_upper_temperature_)}}, {::_pbi::TcParser::MiniParse, {}}, // repeated int32 global_scene_entity_page_ids = 30; {::_pbi::TcParser::FastV32P2, @@ -1540,7 +1650,7 @@ const ::_pbi::TcParseTable<5, 32, 1, 94, 7> NSPanelConfig::_table_ = { {504, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.optimistic_mode_)}}, }}, {{ 33, 0, 1, - 65290, 26, + 61450, 26, 65535, 65535 }}, {{ // string name = 1; @@ -1639,6 +1749,18 @@ const ::_pbi::TcParseTable<5, 32, 1, 94, 7> NSPanelConfig::_table_ = { // string inside_temperature_sensor_mqtt_topic = 40; {PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.inside_temperature_sensor_mqtt_topic_), 0, 0, (0 | ::_fl::kFcSingular | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // int32 button1_lower_temperature = 41; + {PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.button1_lower_temperature_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kInt32)}, + // int32 button1_upper_temperature = 42; + {PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.button1_upper_temperature_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kInt32)}, + // int32 button2_lower_temperature = 43; + {PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.button2_lower_temperature_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kInt32)}, + // int32 button2_upper_temperature = 44; + {PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.button2_upper_temperature_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kInt32)}, }}, {{ {::_pbi::TcParser::GetTable<::NSPanelConfig_RoomInfo>()}, }}, {{ @@ -1663,8 +1785,8 @@ PROTOBUF_NOINLINE void NSPanelConfig::Clear() { _impl_.name_.ClearToEmpty(); _impl_.inside_temperature_sensor_mqtt_topic_.ClearToEmpty(); ::memset(&_impl_.default_room_, 0, static_cast<::size_t>( - reinterpret_cast(&_impl_.default_light_brightess_) - - reinterpret_cast(&_impl_.default_room_)) + sizeof(_impl_.default_light_brightess_)); + reinterpret_cast(&_impl_.button2_upper_temperature_) - + reinterpret_cast(&_impl_.default_room_)) + sizeof(_impl_.button2_upper_temperature_)); _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); } @@ -1911,6 +2033,34 @@ ::uint8_t* NSPanelConfig::_InternalSerialize( target = stream->WriteStringMaybeAliased(40, _s, target); } + // int32 button1_lower_temperature = 41; + if (this->_internal_button1_lower_temperature() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray( + 41, this->_internal_button1_lower_temperature(), target); + } + + // int32 button1_upper_temperature = 42; + if (this->_internal_button1_upper_temperature() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray( + 42, this->_internal_button1_upper_temperature(), target); + } + + // int32 button2_lower_temperature = 43; + if (this->_internal_button2_lower_temperature() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray( + 43, this->_internal_button2_lower_temperature(), target); + } + + // int32 button2_upper_temperature = 44; + if (this->_internal_button2_upper_temperature() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt32ToArray( + 44, this->_internal_button2_upper_temperature(), target); + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( @@ -2131,6 +2281,30 @@ ::size_t NSPanelConfig::ByteSizeLong() const { this->_internal_default_light_brightess()); } + // int32 button1_lower_temperature = 41; + if (this->_internal_button1_lower_temperature() != 0) { + total_size += 2 + ::_pbi::WireFormatLite::Int32Size( + this->_internal_button1_lower_temperature()); + } + + // int32 button1_upper_temperature = 42; + if (this->_internal_button1_upper_temperature() != 0) { + total_size += 2 + ::_pbi::WireFormatLite::Int32Size( + this->_internal_button1_upper_temperature()); + } + + // int32 button2_lower_temperature = 43; + if (this->_internal_button2_lower_temperature() != 0) { + total_size += 2 + ::_pbi::WireFormatLite::Int32Size( + this->_internal_button2_lower_temperature()); + } + + // int32 button2_upper_temperature = 44; + if (this->_internal_button2_upper_temperature() != 0) { + total_size += 2 + ::_pbi::WireFormatLite::Int32Size( + this->_internal_button2_upper_temperature()); + } + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); } @@ -2232,6 +2406,18 @@ void NSPanelConfig::MergeImpl(::google::protobuf::MessageLite& to_msg, const ::g if (from._internal_default_light_brightess() != 0) { _this->_impl_.default_light_brightess_ = from._impl_.default_light_brightess_; } + if (from._internal_button1_lower_temperature() != 0) { + _this->_impl_.button1_lower_temperature_ = from._impl_.button1_lower_temperature_; + } + if (from._internal_button1_upper_temperature() != 0) { + _this->_impl_.button1_upper_temperature_ = from._impl_.button1_upper_temperature_; + } + if (from._internal_button2_lower_temperature() != 0) { + _this->_impl_.button2_lower_temperature_ = from._impl_.button2_lower_temperature_; + } + if (from._internal_button2_upper_temperature() != 0) { + _this->_impl_.button2_upper_temperature_ = from._impl_.button2_upper_temperature_; + } _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>(from._internal_metadata_); } @@ -2255,8 +2441,8 @@ void NSPanelConfig::InternalSwap(NSPanelConfig* PROTOBUF_RESTRICT other) { ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.name_, &other->_impl_.name_, arena); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.inside_temperature_sensor_mqtt_topic_, &other->_impl_.inside_temperature_sensor_mqtt_topic_, arena); ::google::protobuf::internal::memswap< - PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.default_light_brightess_) - + sizeof(NSPanelConfig::_impl_.default_light_brightess_) + PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.button2_upper_temperature_) + + sizeof(NSPanelConfig::_impl_.button2_upper_temperature_) - PROTOBUF_FIELD_OFFSET(NSPanelConfig, _impl_.default_room_)>( reinterpret_cast(&_impl_.default_room_), reinterpret_cast(&other->_impl_.default_room_)); @@ -2519,9 +2705,9 @@ NSPanelStatusReport::NSPanelStatusReport( offsetof(Impl_, nspanel_state_), reinterpret_cast(&from._impl_) + offsetof(Impl_, nspanel_state_), - offsetof(Impl_, temperature_) - + offsetof(Impl_, pressure_) - offsetof(Impl_, nspanel_state_) + - sizeof(Impl_::temperature_)); + sizeof(Impl_::pressure_)); // @@protoc_insertion_point(copy_constructor:NSPanelStatusReport) } @@ -2541,9 +2727,9 @@ inline void NSPanelStatusReport::SharedCtor(::_pb::Arena* arena) { ::memset(reinterpret_cast(&_impl_) + offsetof(Impl_, nspanel_state_), 0, - offsetof(Impl_, temperature_) - + offsetof(Impl_, pressure_) - offsetof(Impl_, nspanel_state_) + - sizeof(Impl_::temperature_)); + sizeof(Impl_::pressure_)); } NSPanelStatusReport::~NSPanelStatusReport() { // @@protoc_insertion_point(destructor:NSPanelStatusReport) @@ -2581,15 +2767,15 @@ NSPanelStatusReport::GetClassData() const { return _data_.base(); } PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 -const ::_pbi::TcParseTable<4, 11, 1, 92, 2> NSPanelStatusReport::_table_ = { +const ::_pbi::TcParseTable<4, 15, 1, 92, 2> NSPanelStatusReport::_table_ = { { 0, // no _has_bits_ 0, // no _extensions_ - 11, 120, // max_field_number, fast_idx_mask + 15, 120, // max_field_number, fast_idx_mask offsetof(decltype(_table_), field_lookup_table), - 4294965248, // skipmap + 4294934528, // skipmap offsetof(decltype(_table_), field_entries), - 11, // num_field_entries + 15, // num_field_entries 1, // num_aux_entries offsetof(decltype(_table_), aux_entries), &_NSPanelStatusReport_default_instance_._instance, @@ -2633,10 +2819,18 @@ const ::_pbi::TcParseTable<4, 11, 1, 92, 2> NSPanelStatusReport::_table_ = { // string md5_tft_gui = 11; {::_pbi::TcParser::FastUS1, {90, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.md5_tft_gui_)}}, - {::_pbi::TcParser::MiniParse, {}}, - {::_pbi::TcParser::MiniParse, {}}, - {::_pbi::TcParser::MiniParse, {}}, - {::_pbi::TcParser::MiniParse, {}}, + // bool has_humidity = 12; + {::_pbi::TcParser::SingularVarintNoZag1(), + {96, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.has_humidity_)}}, + // float humidity = 13; + {::_pbi::TcParser::FastF32S1, + {109, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.humidity_)}}, + // bool has_pressure = 14; + {::_pbi::TcParser::SingularVarintNoZag1(), + {112, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.has_pressure_)}}, + // float pressure = 15; + {::_pbi::TcParser::FastF32S1, + {125, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.pressure_)}}, }}, {{ 65535, 65535 }}, {{ @@ -2673,6 +2867,18 @@ const ::_pbi::TcParseTable<4, 11, 1, 92, 2> NSPanelStatusReport::_table_ = { // string md5_tft_gui = 11; {PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.md5_tft_gui_), 0, 0, (0 | ::_fl::kFcSingular | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // bool has_humidity = 12; + {PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.has_humidity_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kBool)}, + // float humidity = 13; + {PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.humidity_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kFloat)}, + // bool has_pressure = 14; + {PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.has_pressure_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kBool)}, + // float pressure = 15; + {PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.pressure_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kFloat)}, }}, {{ {::_pbi::TcParser::GetTable<::NSPanelWarning>()}, }}, {{ @@ -2700,8 +2906,8 @@ PROTOBUF_NOINLINE void NSPanelStatusReport::Clear() { _impl_.md5_littlefs_.ClearToEmpty(); _impl_.md5_tft_gui_.ClearToEmpty(); ::memset(&_impl_.nspanel_state_, 0, static_cast<::size_t>( - reinterpret_cast(&_impl_.temperature_) - - reinterpret_cast(&_impl_.nspanel_state_)) + sizeof(_impl_.temperature_)); + reinterpret_cast(&_impl_.pressure_) - + reinterpret_cast(&_impl_.nspanel_state_)) + sizeof(_impl_.pressure_)); _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); } @@ -2803,6 +3009,44 @@ ::uint8_t* NSPanelStatusReport::_InternalSerialize( target = stream->WriteStringMaybeAliased(11, _s, target); } + // bool has_humidity = 12; + if (this->_internal_has_humidity() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 12, this->_internal_has_humidity(), target); + } + + // float humidity = 13; + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_humidity = this->_internal_humidity(); + ::uint32_t raw_humidity; + memcpy(&raw_humidity, &tmp_humidity, sizeof(tmp_humidity)); + if (raw_humidity != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray( + 13, this->_internal_humidity(), target); + } + + // bool has_pressure = 14; + if (this->_internal_has_pressure() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 14, this->_internal_has_pressure(), target); + } + + // float pressure = 15; + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_pressure = this->_internal_pressure(); + ::uint32_t raw_pressure; + memcpy(&raw_pressure, &tmp_pressure, sizeof(tmp_pressure)); + if (raw_pressure != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray( + 15, this->_internal_pressure(), target); + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( @@ -2890,6 +3134,36 @@ ::size_t NSPanelStatusReport::ByteSizeLong() const { total_size += 5; } + // float humidity = 13; + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_humidity = this->_internal_humidity(); + ::uint32_t raw_humidity; + memcpy(&raw_humidity, &tmp_humidity, sizeof(tmp_humidity)); + if (raw_humidity != 0) { + total_size += 5; + } + + // bool has_humidity = 12; + if (this->_internal_has_humidity() != 0) { + total_size += 2; + } + + // bool has_pressure = 14; + if (this->_internal_has_pressure() != 0) { + total_size += 2; + } + + // float pressure = 15; + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_pressure = this->_internal_pressure(); + ::uint32_t raw_pressure; + memcpy(&raw_pressure, &tmp_pressure, sizeof(tmp_pressure)); + if (raw_pressure != 0) { + total_size += 5; + } + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); } @@ -2939,6 +3213,28 @@ void NSPanelStatusReport::MergeImpl(::google::protobuf::MessageLite& to_msg, con if (raw_temperature != 0) { _this->_impl_.temperature_ = from._impl_.temperature_; } + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_humidity = from._internal_humidity(); + ::uint32_t raw_humidity; + memcpy(&raw_humidity, &tmp_humidity, sizeof(tmp_humidity)); + if (raw_humidity != 0) { + _this->_impl_.humidity_ = from._impl_.humidity_; + } + if (from._internal_has_humidity() != 0) { + _this->_impl_.has_humidity_ = from._impl_.has_humidity_; + } + if (from._internal_has_pressure() != 0) { + _this->_impl_.has_pressure_ = from._impl_.has_pressure_; + } + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_pressure = from._internal_pressure(); + ::uint32_t raw_pressure; + memcpy(&raw_pressure, &tmp_pressure, sizeof(tmp_pressure)); + if (raw_pressure != 0) { + _this->_impl_.pressure_ = from._impl_.pressure_; + } _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>(from._internal_metadata_); } @@ -2962,8 +3258,8 @@ void NSPanelStatusReport::InternalSwap(NSPanelStatusReport* PROTOBUF_RESTRICT ot ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.md5_littlefs_, &other->_impl_.md5_littlefs_, arena); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.md5_tft_gui_, &other->_impl_.md5_tft_gui_, arena); ::google::protobuf::internal::memswap< - PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.temperature_) - + sizeof(NSPanelStatusReport::_impl_.temperature_) + PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.pressure_) + + sizeof(NSPanelStatusReport::_impl_.pressure_) - PROTOBUF_FIELD_OFFSET(NSPanelStatusReport, _impl_.nspanel_state_)>( reinterpret_cast(&_impl_.nspanel_state_), reinterpret_cast(&other->_impl_.nspanel_state_)); @@ -6743,107 +7039,603 @@ ::google::protobuf::Metadata NSPanelMQTTManagerCommand_ButtonPressed::GetMetadat } // =================================================================== -class NSPanelMQTTManagerCommand::_Internal { +class NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::_Internal { public: - static constexpr ::int32_t kOneofCaseOffset = - PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand, _impl_._oneof_case_); }; -void NSPanelMQTTManagerCommand::set_allocated_first_page_turn_on(::NSPanelMQTTManagerCommand_FirstPageTurnLightOn* first_page_turn_on) { - ::google::protobuf::Arena* message_arena = GetArena(); - clear_CommandData(); - if (first_page_turn_on) { - ::google::protobuf::Arena* submessage_arena = first_page_turn_on->GetArena(); - if (message_arena != submessage_arena) { - first_page_turn_on = ::google::protobuf::internal::GetOwnedMessage(message_arena, first_page_turn_on, submessage_arena); - } - set_has_first_page_turn_on(); - _impl_.CommandData_.first_page_turn_on_ = first_page_turn_on; - } - // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.first_page_turn_on) -} -void NSPanelMQTTManagerCommand::set_allocated_first_page_turn_off(::NSPanelMQTTManagerCommand_FirstPageTurnLightOff* first_page_turn_off) { - ::google::protobuf::Arena* message_arena = GetArena(); - clear_CommandData(); - if (first_page_turn_off) { - ::google::protobuf::Arena* submessage_arena = first_page_turn_off->GetArena(); - if (message_arena != submessage_arena) { - first_page_turn_off = ::google::protobuf::internal::GetOwnedMessage(message_arena, first_page_turn_off, submessage_arena); - } - set_has_first_page_turn_off(); - _impl_.CommandData_.first_page_turn_off_ = first_page_turn_off; - } - // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.first_page_turn_off) -} -void NSPanelMQTTManagerCommand::set_allocated_light_command(::NSPanelMQTTManagerCommand_LightCommand* light_command) { - ::google::protobuf::Arena* message_arena = GetArena(); - clear_CommandData(); - if (light_command) { - ::google::protobuf::Arena* submessage_arena = light_command->GetArena(); - if (message_arena != submessage_arena) { - light_command = ::google::protobuf::internal::GetOwnedMessage(message_arena, light_command, submessage_arena); - } - set_has_light_command(); - _impl_.CommandData_.light_command_ = light_command; - } - // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.light_command) +NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand(::google::protobuf::Arena* arena) + : ::google::protobuf::Message(arena) { + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand) } -void NSPanelMQTTManagerCommand::set_allocated_toggle_entity_from_entities_page(::NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPage* toggle_entity_from_entities_page) { - ::google::protobuf::Arena* message_arena = GetArena(); - clear_CommandData(); - if (toggle_entity_from_entities_page) { - ::google::protobuf::Arena* submessage_arena = toggle_entity_from_entities_page->GetArena(); - if (message_arena != submessage_arena) { - toggle_entity_from_entities_page = ::google::protobuf::internal::GetOwnedMessage(message_arena, toggle_entity_from_entities_page, submessage_arena); - } - set_has_toggle_entity_from_entities_page(); - _impl_.CommandData_.toggle_entity_from_entities_page_ = toggle_entity_from_entities_page; - } - // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.toggle_entity_from_entities_page) +NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand( + ::google::protobuf::Arena* arena, const NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& from) + : NSPanelMQTTManagerCommand_ThermostatTemperatureCommand(arena) { + MergeFrom(from); } -void NSPanelMQTTManagerCommand::set_allocated_save_scene_command(::NSPanelMQTTManagerCommand_SaveSceneCommand* save_scene_command) { - ::google::protobuf::Arena* message_arena = GetArena(); - clear_CommandData(); - if (save_scene_command) { - ::google::protobuf::Arena* submessage_arena = save_scene_command->GetArena(); - if (message_arena != submessage_arena) { - save_scene_command = ::google::protobuf::internal::GetOwnedMessage(message_arena, save_scene_command, submessage_arena); - } - set_has_save_scene_command(); - _impl_.CommandData_.save_scene_command_ = save_scene_command; - } - // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.save_scene_command) +inline PROTOBUF_NDEBUG_INLINE NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::Impl_::Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena) + : _cached_size_{0} {} + +inline void NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::SharedCtor(::_pb::Arena* arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, thermostat_id_), + 0, + offsetof(Impl_, temperature_) - + offsetof(Impl_, thermostat_id_) + + sizeof(Impl_::temperature_)); } -void NSPanelMQTTManagerCommand::set_allocated_button_pressed(::NSPanelMQTTManagerCommand_ButtonPressed* button_pressed) { - ::google::protobuf::Arena* message_arena = GetArena(); - clear_CommandData(); - if (button_pressed) { - ::google::protobuf::Arena* submessage_arena = button_pressed->GetArena(); - if (message_arena != submessage_arena) { - button_pressed = ::google::protobuf::internal::GetOwnedMessage(message_arena, button_pressed, submessage_arena); - } - set_has_button_pressed(); - _impl_.CommandData_.button_pressed_ = button_pressed; - } - // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.button_pressed) +NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::~NSPanelMQTTManagerCommand_ThermostatTemperatureCommand() { + // @@protoc_insertion_point(destructor:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand) + _internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + SharedDtor(); } -NSPanelMQTTManagerCommand::NSPanelMQTTManagerCommand(::google::protobuf::Arena* arena) - : ::google::protobuf::Message(arena) { - SharedCtor(arena); - // @@protoc_insertion_point(arena_constructor:NSPanelMQTTManagerCommand) +inline void NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::SharedDtor() { + ABSL_DCHECK(GetArena() == nullptr); + _impl_.~Impl_(); } -inline PROTOBUF_NDEBUG_INLINE NSPanelMQTTManagerCommand::Impl_::Impl_( - ::google::protobuf::internal::InternalVisibility visibility, ::google::protobuf::Arena* arena, - const Impl_& from, const ::NSPanelMQTTManagerCommand& from_msg) - : CommandData_{}, - _cached_size_{0}, - _oneof_case_{from._oneof_case_[0]} {} -NSPanelMQTTManagerCommand::NSPanelMQTTManagerCommand( - ::google::protobuf::Arena* arena, - const NSPanelMQTTManagerCommand& from) - : ::google::protobuf::Message(arena) { - NSPanelMQTTManagerCommand* const _this = this; +const ::google::protobuf::MessageLite::ClassData* +NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::GetClassData() const { + PROTOBUF_CONSTINIT static const ::google::protobuf::MessageLite:: + ClassDataFull _data_ = { + { + &_table_.header, + nullptr, // OnDemandRegisterArenaDtor + nullptr, // IsInitialized + PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand, _impl_._cached_size_), + false, + }, + &NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::MergeImpl, + &NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::kDescriptorMethods, + &descriptor_table_protobuf_5fnspanel_2eproto, + nullptr, // tracker + }; + ::google::protobuf::internal::PrefetchToLocalCache(&_data_); + ::google::protobuf::internal::PrefetchToLocalCache(_data_.tc_table); + return _data_.base(); +} +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 +const ::_pbi::TcParseTable<1, 2, 0, 0, 2> NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::_table_ = { + { + 0, // no _has_bits_ + 0, // no _extensions_ + 2, 8, // max_field_number, fast_idx_mask + offsetof(decltype(_table_), field_lookup_table), + 4294967292, // skipmap + offsetof(decltype(_table_), field_entries), + 2, // num_field_entries + 0, // num_aux_entries + offsetof(decltype(_table_), field_names), // no aux_entries + &_NSPanelMQTTManagerCommand_ThermostatTemperatureCommand_default_instance_._instance, + nullptr, // post_loop_handler + ::_pbi::TcParser::GenericFallback, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // float temperature = 2; + {::_pbi::TcParser::FastF32S1, + {21, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand, _impl_.temperature_)}}, + // int32 thermostat_id = 1; + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand, _impl_.thermostat_id_), 63>(), + {8, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand, _impl_.thermostat_id_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // int32 thermostat_id = 1; + {PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand, _impl_.thermostat_id_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kInt32)}, + // float temperature = 2; + {PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand, _impl_.temperature_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kFloat)}, + }}, + // no aux_entries + {{ + }}, +}; + +PROTOBUF_NOINLINE void NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::Clear() { +// @@protoc_insertion_point(message_clear_start:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + ::memset(&_impl_.thermostat_id_, 0, static_cast<::size_t>( + reinterpret_cast(&_impl_.temperature_) - + reinterpret_cast(&_impl_.thermostat_id_)) + sizeof(_impl_.temperature_)); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +::uint8_t* NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::_InternalSerialize( + ::uint8_t* target, + ::google::protobuf::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + // int32 thermostat_id = 1; + if (this->_internal_thermostat_id() != 0) { + target = ::google::protobuf::internal::WireFormatLite:: + WriteInt32ToArrayWithField<1>( + stream, this->_internal_thermostat_id(), target); + } + + // float temperature = 2; + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_temperature = this->_internal_temperature(); + ::uint32_t raw_temperature; + memcpy(&raw_temperature, &tmp_temperature, sizeof(tmp_temperature)); + if (raw_temperature != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray( + 2, this->_internal_temperature(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand) + return target; +} + +::size_t NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + ::_pbi::Prefetch5LinesFrom7Lines(reinterpret_cast(this)); + // int32 thermostat_id = 1; + if (this->_internal_thermostat_id() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne( + this->_internal_thermostat_id()); + } + + // float temperature = 2; + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_temperature = this->_internal_temperature(); + ::uint32_t raw_temperature; + memcpy(&raw_temperature, &tmp_temperature, sizeof(tmp_temperature)); + if (raw_temperature != 0) { + total_size += 5; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + + +void NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::MergeImpl(::google::protobuf::MessageLite& to_msg, const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (from._internal_thermostat_id() != 0) { + _this->_impl_.thermostat_id_ = from._impl_.thermostat_id_; + } + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_temperature = from._internal_temperature(); + ::uint32_t raw_temperature; + memcpy(&raw_temperature, &tmp_temperature, sizeof(tmp_temperature)); + if (raw_temperature != 0) { + _this->_impl_.temperature_ = from._impl_.temperature_; + } + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>(from._internal_metadata_); +} + +void NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::CopyFrom(const NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::InternalSwap(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* PROTOBUF_RESTRICT other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand, _impl_.temperature_) + + sizeof(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::_impl_.temperature_) + - PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand, _impl_.thermostat_id_)>( + reinterpret_cast(&_impl_.thermostat_id_), + reinterpret_cast(&other->_impl_.thermostat_id_)); +} + +::google::protobuf::Metadata NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::GetMetadata() const { + return ::google::protobuf::Message::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +class NSPanelMQTTManagerCommand_ThermostatCommand::_Internal { + public: +}; + +NSPanelMQTTManagerCommand_ThermostatCommand::NSPanelMQTTManagerCommand_ThermostatCommand(::google::protobuf::Arena* arena) + : ::google::protobuf::Message(arena) { + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:NSPanelMQTTManagerCommand.ThermostatCommand) +} +inline PROTOBUF_NDEBUG_INLINE NSPanelMQTTManagerCommand_ThermostatCommand::Impl_::Impl_( + ::google::protobuf::internal::InternalVisibility visibility, ::google::protobuf::Arena* arena, + const Impl_& from, const ::NSPanelMQTTManagerCommand_ThermostatCommand& from_msg) + : option_(arena, from.option_), + new_value_(arena, from.new_value_), + _cached_size_{0} {} + +NSPanelMQTTManagerCommand_ThermostatCommand::NSPanelMQTTManagerCommand_ThermostatCommand( + ::google::protobuf::Arena* arena, + const NSPanelMQTTManagerCommand_ThermostatCommand& from) + : ::google::protobuf::Message(arena) { + NSPanelMQTTManagerCommand_ThermostatCommand* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + _impl_.thermostat_id_ = from._impl_.thermostat_id_; + + // @@protoc_insertion_point(copy_constructor:NSPanelMQTTManagerCommand.ThermostatCommand) +} +inline PROTOBUF_NDEBUG_INLINE NSPanelMQTTManagerCommand_ThermostatCommand::Impl_::Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena) + : option_(arena), + new_value_(arena), + _cached_size_{0} {} + +inline void NSPanelMQTTManagerCommand_ThermostatCommand::SharedCtor(::_pb::Arena* arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + _impl_.thermostat_id_ = {}; +} +NSPanelMQTTManagerCommand_ThermostatCommand::~NSPanelMQTTManagerCommand_ThermostatCommand() { + // @@protoc_insertion_point(destructor:NSPanelMQTTManagerCommand.ThermostatCommand) + _internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + SharedDtor(); +} +inline void NSPanelMQTTManagerCommand_ThermostatCommand::SharedDtor() { + ABSL_DCHECK(GetArena() == nullptr); + _impl_.option_.Destroy(); + _impl_.new_value_.Destroy(); + _impl_.~Impl_(); +} + +const ::google::protobuf::MessageLite::ClassData* +NSPanelMQTTManagerCommand_ThermostatCommand::GetClassData() const { + PROTOBUF_CONSTINIT static const ::google::protobuf::MessageLite:: + ClassDataFull _data_ = { + { + &_table_.header, + nullptr, // OnDemandRegisterArenaDtor + nullptr, // IsInitialized + PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatCommand, _impl_._cached_size_), + false, + }, + &NSPanelMQTTManagerCommand_ThermostatCommand::MergeImpl, + &NSPanelMQTTManagerCommand_ThermostatCommand::kDescriptorMethods, + &descriptor_table_protobuf_5fnspanel_2eproto, + nullptr, // tracker + }; + ::google::protobuf::internal::PrefetchToLocalCache(&_data_); + ::google::protobuf::internal::PrefetchToLocalCache(_data_.tc_table); + return _data_.base(); +} +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 +const ::_pbi::TcParseTable<2, 3, 0, 67, 2> NSPanelMQTTManagerCommand_ThermostatCommand::_table_ = { + { + 0, // no _has_bits_ + 0, // no _extensions_ + 3, 24, // max_field_number, fast_idx_mask + offsetof(decltype(_table_), field_lookup_table), + 4294967288, // skipmap + offsetof(decltype(_table_), field_entries), + 3, // num_field_entries + 0, // num_aux_entries + offsetof(decltype(_table_), field_names), // no aux_entries + &_NSPanelMQTTManagerCommand_ThermostatCommand_default_instance_._instance, + nullptr, // post_loop_handler + ::_pbi::TcParser::GenericFallback, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::NSPanelMQTTManagerCommand_ThermostatCommand>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // int32 thermostat_id = 1; + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(NSPanelMQTTManagerCommand_ThermostatCommand, _impl_.thermostat_id_), 63>(), + {8, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatCommand, _impl_.thermostat_id_)}}, + // string option = 2; + {::_pbi::TcParser::FastUS1, + {18, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatCommand, _impl_.option_)}}, + // string new_value = 3; + {::_pbi::TcParser::FastUS1, + {26, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatCommand, _impl_.new_value_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // int32 thermostat_id = 1; + {PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatCommand, _impl_.thermostat_id_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kInt32)}, + // string option = 2; + {PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatCommand, _impl_.option_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string new_value = 3; + {PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand_ThermostatCommand, _impl_.new_value_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kUtf8String | ::_fl::kRepAString)}, + }}, + // no aux_entries + {{ + "\53\0\6\11\0\0\0\0" + "NSPanelMQTTManagerCommand.ThermostatCommand" + "option" + "new_value" + }}, +}; + +PROTOBUF_NOINLINE void NSPanelMQTTManagerCommand_ThermostatCommand::Clear() { +// @@protoc_insertion_point(message_clear_start:NSPanelMQTTManagerCommand.ThermostatCommand) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.option_.ClearToEmpty(); + _impl_.new_value_.ClearToEmpty(); + _impl_.thermostat_id_ = 0; + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +::uint8_t* NSPanelMQTTManagerCommand_ThermostatCommand::_InternalSerialize( + ::uint8_t* target, + ::google::protobuf::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:NSPanelMQTTManagerCommand.ThermostatCommand) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + // int32 thermostat_id = 1; + if (this->_internal_thermostat_id() != 0) { + target = ::google::protobuf::internal::WireFormatLite:: + WriteInt32ToArrayWithField<1>( + stream, this->_internal_thermostat_id(), target); + } + + // string option = 2; + if (!this->_internal_option().empty()) { + const std::string& _s = this->_internal_option(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "NSPanelMQTTManagerCommand.ThermostatCommand.option"); + target = stream->WriteStringMaybeAliased(2, _s, target); + } + + // string new_value = 3; + if (!this->_internal_new_value().empty()) { + const std::string& _s = this->_internal_new_value(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "NSPanelMQTTManagerCommand.ThermostatCommand.new_value"); + target = stream->WriteStringMaybeAliased(3, _s, target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:NSPanelMQTTManagerCommand.ThermostatCommand) + return target; +} + +::size_t NSPanelMQTTManagerCommand_ThermostatCommand::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:NSPanelMQTTManagerCommand.ThermostatCommand) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + ::_pbi::Prefetch5LinesFrom7Lines(reinterpret_cast(this)); + // string option = 2; + if (!this->_internal_option().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this->_internal_option()); + } + + // string new_value = 3; + if (!this->_internal_new_value().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this->_internal_new_value()); + } + + // int32 thermostat_id = 1; + if (this->_internal_thermostat_id() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne( + this->_internal_thermostat_id()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + + +void NSPanelMQTTManagerCommand_ThermostatCommand::MergeImpl(::google::protobuf::MessageLite& to_msg, const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:NSPanelMQTTManagerCommand.ThermostatCommand) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_option().empty()) { + _this->_internal_set_option(from._internal_option()); + } + if (!from._internal_new_value().empty()) { + _this->_internal_set_new_value(from._internal_new_value()); + } + if (from._internal_thermostat_id() != 0) { + _this->_impl_.thermostat_id_ = from._impl_.thermostat_id_; + } + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>(from._internal_metadata_); +} + +void NSPanelMQTTManagerCommand_ThermostatCommand::CopyFrom(const NSPanelMQTTManagerCommand_ThermostatCommand& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:NSPanelMQTTManagerCommand.ThermostatCommand) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void NSPanelMQTTManagerCommand_ThermostatCommand::InternalSwap(NSPanelMQTTManagerCommand_ThermostatCommand* PROTOBUF_RESTRICT other) { + using std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.option_, &other->_impl_.option_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.new_value_, &other->_impl_.new_value_, arena); + swap(_impl_.thermostat_id_, other->_impl_.thermostat_id_); +} + +::google::protobuf::Metadata NSPanelMQTTManagerCommand_ThermostatCommand::GetMetadata() const { + return ::google::protobuf::Message::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +class NSPanelMQTTManagerCommand::_Internal { + public: + static constexpr ::int32_t kOneofCaseOffset = + PROTOBUF_FIELD_OFFSET(::NSPanelMQTTManagerCommand, _impl_._oneof_case_); +}; + +void NSPanelMQTTManagerCommand::set_allocated_first_page_turn_on(::NSPanelMQTTManagerCommand_FirstPageTurnLightOn* first_page_turn_on) { + ::google::protobuf::Arena* message_arena = GetArena(); + clear_CommandData(); + if (first_page_turn_on) { + ::google::protobuf::Arena* submessage_arena = first_page_turn_on->GetArena(); + if (message_arena != submessage_arena) { + first_page_turn_on = ::google::protobuf::internal::GetOwnedMessage(message_arena, first_page_turn_on, submessage_arena); + } + set_has_first_page_turn_on(); + _impl_.CommandData_.first_page_turn_on_ = first_page_turn_on; + } + // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.first_page_turn_on) +} +void NSPanelMQTTManagerCommand::set_allocated_first_page_turn_off(::NSPanelMQTTManagerCommand_FirstPageTurnLightOff* first_page_turn_off) { + ::google::protobuf::Arena* message_arena = GetArena(); + clear_CommandData(); + if (first_page_turn_off) { + ::google::protobuf::Arena* submessage_arena = first_page_turn_off->GetArena(); + if (message_arena != submessage_arena) { + first_page_turn_off = ::google::protobuf::internal::GetOwnedMessage(message_arena, first_page_turn_off, submessage_arena); + } + set_has_first_page_turn_off(); + _impl_.CommandData_.first_page_turn_off_ = first_page_turn_off; + } + // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.first_page_turn_off) +} +void NSPanelMQTTManagerCommand::set_allocated_light_command(::NSPanelMQTTManagerCommand_LightCommand* light_command) { + ::google::protobuf::Arena* message_arena = GetArena(); + clear_CommandData(); + if (light_command) { + ::google::protobuf::Arena* submessage_arena = light_command->GetArena(); + if (message_arena != submessage_arena) { + light_command = ::google::protobuf::internal::GetOwnedMessage(message_arena, light_command, submessage_arena); + } + set_has_light_command(); + _impl_.CommandData_.light_command_ = light_command; + } + // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.light_command) +} +void NSPanelMQTTManagerCommand::set_allocated_toggle_entity_from_entities_page(::NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPage* toggle_entity_from_entities_page) { + ::google::protobuf::Arena* message_arena = GetArena(); + clear_CommandData(); + if (toggle_entity_from_entities_page) { + ::google::protobuf::Arena* submessage_arena = toggle_entity_from_entities_page->GetArena(); + if (message_arena != submessage_arena) { + toggle_entity_from_entities_page = ::google::protobuf::internal::GetOwnedMessage(message_arena, toggle_entity_from_entities_page, submessage_arena); + } + set_has_toggle_entity_from_entities_page(); + _impl_.CommandData_.toggle_entity_from_entities_page_ = toggle_entity_from_entities_page; + } + // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.toggle_entity_from_entities_page) +} +void NSPanelMQTTManagerCommand::set_allocated_save_scene_command(::NSPanelMQTTManagerCommand_SaveSceneCommand* save_scene_command) { + ::google::protobuf::Arena* message_arena = GetArena(); + clear_CommandData(); + if (save_scene_command) { + ::google::protobuf::Arena* submessage_arena = save_scene_command->GetArena(); + if (message_arena != submessage_arena) { + save_scene_command = ::google::protobuf::internal::GetOwnedMessage(message_arena, save_scene_command, submessage_arena); + } + set_has_save_scene_command(); + _impl_.CommandData_.save_scene_command_ = save_scene_command; + } + // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.save_scene_command) +} +void NSPanelMQTTManagerCommand::set_allocated_button_pressed(::NSPanelMQTTManagerCommand_ButtonPressed* button_pressed) { + ::google::protobuf::Arena* message_arena = GetArena(); + clear_CommandData(); + if (button_pressed) { + ::google::protobuf::Arena* submessage_arena = button_pressed->GetArena(); + if (message_arena != submessage_arena) { + button_pressed = ::google::protobuf::internal::GetOwnedMessage(message_arena, button_pressed, submessage_arena); + } + set_has_button_pressed(); + _impl_.CommandData_.button_pressed_ = button_pressed; + } + // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.button_pressed) +} +void NSPanelMQTTManagerCommand::set_allocated_thermostat_temperature_command(::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* thermostat_temperature_command) { + ::google::protobuf::Arena* message_arena = GetArena(); + clear_CommandData(); + if (thermostat_temperature_command) { + ::google::protobuf::Arena* submessage_arena = thermostat_temperature_command->GetArena(); + if (message_arena != submessage_arena) { + thermostat_temperature_command = ::google::protobuf::internal::GetOwnedMessage(message_arena, thermostat_temperature_command, submessage_arena); + } + set_has_thermostat_temperature_command(); + _impl_.CommandData_.thermostat_temperature_command_ = thermostat_temperature_command; + } + // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.thermostat_temperature_command) +} +void NSPanelMQTTManagerCommand::set_allocated_thermostat_command(::NSPanelMQTTManagerCommand_ThermostatCommand* thermostat_command) { + ::google::protobuf::Arena* message_arena = GetArena(); + clear_CommandData(); + if (thermostat_command) { + ::google::protobuf::Arena* submessage_arena = thermostat_command->GetArena(); + if (message_arena != submessage_arena) { + thermostat_command = ::google::protobuf::internal::GetOwnedMessage(message_arena, thermostat_command, submessage_arena); + } + set_has_thermostat_command(); + _impl_.CommandData_.thermostat_command_ = thermostat_command; + } + // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.thermostat_command) +} +NSPanelMQTTManagerCommand::NSPanelMQTTManagerCommand(::google::protobuf::Arena* arena) + : ::google::protobuf::Message(arena) { + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:NSPanelMQTTManagerCommand) +} +inline PROTOBUF_NDEBUG_INLINE NSPanelMQTTManagerCommand::Impl_::Impl_( + ::google::protobuf::internal::InternalVisibility visibility, ::google::protobuf::Arena* arena, + const Impl_& from, const ::NSPanelMQTTManagerCommand& from_msg) + : CommandData_{}, + _cached_size_{0}, + _oneof_case_{from._oneof_case_[0]} {} + +NSPanelMQTTManagerCommand::NSPanelMQTTManagerCommand( + ::google::protobuf::Arena* arena, + const NSPanelMQTTManagerCommand& from) + : ::google::protobuf::Message(arena) { + NSPanelMQTTManagerCommand* const _this = this; (void)_this; _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( from._internal_metadata_); @@ -6870,6 +7662,12 @@ NSPanelMQTTManagerCommand::NSPanelMQTTManagerCommand( case kButtonPressed: _impl_.CommandData_.button_pressed_ = ::google::protobuf::Message::CopyConstruct<::NSPanelMQTTManagerCommand_ButtonPressed>(arena, *from._impl_.CommandData_.button_pressed_); break; + case kThermostatTemperatureCommand: + _impl_.CommandData_.thermostat_temperature_command_ = ::google::protobuf::Message::CopyConstruct<::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand>(arena, *from._impl_.CommandData_.thermostat_temperature_command_); + break; + case kThermostatCommand: + _impl_.CommandData_.thermostat_command_ = ::google::protobuf::Message::CopyConstruct<::NSPanelMQTTManagerCommand_ThermostatCommand>(arena, *from._impl_.CommandData_.thermostat_command_); + break; } // @@protoc_insertion_point(copy_constructor:NSPanelMQTTManagerCommand) @@ -6950,6 +7748,22 @@ void NSPanelMQTTManagerCommand::clear_CommandData() { } break; } + case kThermostatTemperatureCommand: { + if (GetArena() == nullptr) { + delete _impl_.CommandData_.thermostat_temperature_command_; + } else if (::google::protobuf::internal::DebugHardenClearOneofMessageOnArena()) { + ::google::protobuf::internal::MaybePoisonAfterClear(_impl_.CommandData_.thermostat_temperature_command_); + } + break; + } + case kThermostatCommand: { + if (GetArena() == nullptr) { + delete _impl_.CommandData_.thermostat_command_; + } else if (::google::protobuf::internal::DebugHardenClearOneofMessageOnArena()) { + ::google::protobuf::internal::MaybePoisonAfterClear(_impl_.CommandData_.thermostat_command_); + } + break; + } case COMMANDDATA_NOT_SET: { break; } @@ -6979,16 +7793,16 @@ NSPanelMQTTManagerCommand::GetClassData() const { return _data_.base(); } PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 -const ::_pbi::TcParseTable<0, 7, 6, 0, 7> NSPanelMQTTManagerCommand::_table_ = { +const ::_pbi::TcParseTable<0, 9, 8, 0, 7> NSPanelMQTTManagerCommand::_table_ = { { 0, // no _has_bits_ 0, // no _extensions_ 100, 0, // max_field_number, fast_idx_mask offsetof(decltype(_table_), field_lookup_table), - 4294967232, // skipmap + 4294967040, // skipmap offsetof(decltype(_table_), field_entries), - 7, // num_field_entries - 6, // num_aux_entries + 9, // num_field_entries + 8, // num_aux_entries offsetof(decltype(_table_), aux_entries), &_NSPanelMQTTManagerCommand_default_instance_._instance, nullptr, // post_loop_handler @@ -7002,7 +7816,7 @@ const ::_pbi::TcParseTable<0, 7, 6, 0, 7> NSPanelMQTTManagerCommand::_table_ = { {1696, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand, _impl_.nspanel_id_)}}, }}, {{ 100, 0, 1, - 65534, 6, + 65534, 8, 65535, 65535 }}, {{ // .NSPanelMQTTManagerCommand.FirstPageTurnLightOn first_page_turn_on = 1; @@ -7023,6 +7837,12 @@ const ::_pbi::TcParseTable<0, 7, 6, 0, 7> NSPanelMQTTManagerCommand::_table_ = { // .NSPanelMQTTManagerCommand.ButtonPressed button_pressed = 6; {PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand, _impl_.CommandData_.button_pressed_), _Internal::kOneofCaseOffset + 0, 5, (0 | ::_fl::kFcOneof | ::_fl::kMessage | ::_fl::kTvTable)}, + // .NSPanelMQTTManagerCommand.ThermostatTemperatureCommand thermostat_temperature_command = 7; + {PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand, _impl_.CommandData_.thermostat_temperature_command_), _Internal::kOneofCaseOffset + 0, 6, + (0 | ::_fl::kFcOneof | ::_fl::kMessage | ::_fl::kTvTable)}, + // .NSPanelMQTTManagerCommand.ThermostatCommand thermostat_command = 8; + {PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand, _impl_.CommandData_.thermostat_command_), _Internal::kOneofCaseOffset + 0, 7, + (0 | ::_fl::kFcOneof | ::_fl::kMessage | ::_fl::kTvTable)}, // int32 nspanel_id = 100; {PROTOBUF_FIELD_OFFSET(NSPanelMQTTManagerCommand, _impl_.nspanel_id_), 0, 0, (0 | ::_fl::kFcSingular | ::_fl::kInt32)}, @@ -7033,6 +7853,8 @@ const ::_pbi::TcParseTable<0, 7, 6, 0, 7> NSPanelMQTTManagerCommand::_table_ = { {::_pbi::TcParser::GetTable<::NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPage>()}, {::_pbi::TcParser::GetTable<::NSPanelMQTTManagerCommand_SaveSceneCommand>()}, {::_pbi::TcParser::GetTable<::NSPanelMQTTManagerCommand_ButtonPressed>()}, + {::_pbi::TcParser::GetTable<::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand>()}, + {::_pbi::TcParser::GetTable<::NSPanelMQTTManagerCommand_ThermostatCommand>()}, }}, {{ }}, }; @@ -7087,6 +7909,16 @@ ::uint8_t* NSPanelMQTTManagerCommand::_InternalSerialize( 6, *_impl_.CommandData_.button_pressed_, _impl_.CommandData_.button_pressed_->GetCachedSize(), target, stream); break; } + case kThermostatTemperatureCommand: { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 7, *_impl_.CommandData_.thermostat_temperature_command_, _impl_.CommandData_.thermostat_temperature_command_->GetCachedSize(), target, stream); + break; + } + case kThermostatCommand: { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 8, *_impl_.CommandData_.thermostat_command_, _impl_.CommandData_.thermostat_command_->GetCachedSize(), target, stream); + break; + } default: break; } @@ -7157,6 +7989,18 @@ ::size_t NSPanelMQTTManagerCommand::ByteSizeLong() const { 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*_impl_.CommandData_.button_pressed_); break; } + // .NSPanelMQTTManagerCommand.ThermostatTemperatureCommand thermostat_temperature_command = 7; + case kThermostatTemperatureCommand: { + total_size += + 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*_impl_.CommandData_.thermostat_temperature_command_); + break; + } + // .NSPanelMQTTManagerCommand.ThermostatCommand thermostat_command = 8; + case kThermostatCommand: { + total_size += + 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*_impl_.CommandData_.thermostat_command_); + break; + } case COMMANDDATA_NOT_SET: { break; } @@ -7242,6 +8086,24 @@ void NSPanelMQTTManagerCommand::MergeImpl(::google::protobuf::MessageLite& to_ms } break; } + case kThermostatTemperatureCommand: { + if (oneof_needs_init) { + _this->_impl_.CommandData_.thermostat_temperature_command_ = + ::google::protobuf::Message::CopyConstruct<::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand>(arena, *from._impl_.CommandData_.thermostat_temperature_command_); + } else { + _this->_impl_.CommandData_.thermostat_temperature_command_->MergeFrom(from._internal_thermostat_temperature_command()); + } + break; + } + case kThermostatCommand: { + if (oneof_needs_init) { + _this->_impl_.CommandData_.thermostat_command_ = + ::google::protobuf::Message::CopyConstruct<::NSPanelMQTTManagerCommand_ThermostatCommand>(arena, *from._impl_.CommandData_.thermostat_command_); + } else { + _this->_impl_.CommandData_.thermostat_command_->MergeFrom(from._internal_thermostat_command()); + } + break; + } case COMMANDDATA_NOT_SET: break; } diff --git a/docker/MQTTManager/include/protobuf/protobuf_nspanel.pb.h b/docker/MQTTManager/include/protobuf/protobuf_nspanel.pb.h index dbf2212b..00e69d88 100644 --- a/docker/MQTTManager/include/protobuf/protobuf_nspanel.pb.h +++ b/docker/MQTTManager/include/protobuf/protobuf_nspanel.pb.h @@ -77,6 +77,12 @@ extern NSPanelMQTTManagerCommand_LightCommandDefaultTypeInternal _NSPanelMQTTMan class NSPanelMQTTManagerCommand_SaveSceneCommand; struct NSPanelMQTTManagerCommand_SaveSceneCommandDefaultTypeInternal; extern NSPanelMQTTManagerCommand_SaveSceneCommandDefaultTypeInternal _NSPanelMQTTManagerCommand_SaveSceneCommand_default_instance_; +class NSPanelMQTTManagerCommand_ThermostatCommand; +struct NSPanelMQTTManagerCommand_ThermostatCommandDefaultTypeInternal; +extern NSPanelMQTTManagerCommand_ThermostatCommandDefaultTypeInternal _NSPanelMQTTManagerCommand_ThermostatCommand_default_instance_; +class NSPanelMQTTManagerCommand_ThermostatTemperatureCommand; +struct NSPanelMQTTManagerCommand_ThermostatTemperatureCommandDefaultTypeInternal; +extern NSPanelMQTTManagerCommand_ThermostatTemperatureCommandDefaultTypeInternal _NSPanelMQTTManagerCommand_ThermostatTemperatureCommand_default_instance_; class NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPage; struct NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPageDefaultTypeInternal; extern NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPageDefaultTypeInternal _NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPage_default_instance_; @@ -180,6 +186,8 @@ enum NSPanelConfig_NSPanelButtonMode : int { NSPanelConfig_NSPanelButtonMode_DIRECT = 0, NSPanelConfig_NSPanelButtonMode_FOLLOW = 1, NSPanelConfig_NSPanelButtonMode_NOTIFY_MANAGER = 2, + NSPanelConfig_NSPanelButtonMode_THERMOSTAT_HEAT = 3, + NSPanelConfig_NSPanelButtonMode_THERMOSTAT_COOL = 4, NSPanelConfig_NSPanelButtonMode_NSPanelConfig_NSPanelButtonMode_INT_MIN_SENTINEL_DO_NOT_USE_ = std::numeric_limits<::int32_t>::min(), NSPanelConfig_NSPanelButtonMode_NSPanelConfig_NSPanelButtonMode_INT_MAX_SENTINEL_DO_NOT_USE_ = @@ -189,8 +197,8 @@ enum NSPanelConfig_NSPanelButtonMode : int { bool NSPanelConfig_NSPanelButtonMode_IsValid(int value); extern const uint32_t NSPanelConfig_NSPanelButtonMode_internal_data_[]; constexpr NSPanelConfig_NSPanelButtonMode NSPanelConfig_NSPanelButtonMode_NSPanelButtonMode_MIN = static_cast(0); -constexpr NSPanelConfig_NSPanelButtonMode NSPanelConfig_NSPanelButtonMode_NSPanelButtonMode_MAX = static_cast(2); -constexpr int NSPanelConfig_NSPanelButtonMode_NSPanelButtonMode_ARRAYSIZE = 2 + 1; +constexpr NSPanelConfig_NSPanelButtonMode NSPanelConfig_NSPanelButtonMode_NSPanelButtonMode_MAX = static_cast(4); +constexpr int NSPanelConfig_NSPanelButtonMode_NSPanelButtonMode_ARRAYSIZE = 4 + 1; const ::google::protobuf::EnumDescriptor* NSPanelConfig_NSPanelButtonMode_descriptor(); template @@ -203,7 +211,7 @@ const std::string& NSPanelConfig_NSPanelButtonMode_Name(T value) { template <> inline const std::string& NSPanelConfig_NSPanelButtonMode_Name(NSPanelConfig_NSPanelButtonMode value) { return ::google::protobuf::internal::NameOfDenseEnum( + 0, 4>( static_cast(value)); } inline bool NSPanelConfig_NSPanelButtonMode_Parse(absl::string_view name, NSPanelConfig_NSPanelButtonMode* value) { @@ -1535,6 +1543,396 @@ class NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPage final : public ::go }; // ------------------------------------------------------------------- +class NSPanelMQTTManagerCommand_ThermostatTemperatureCommand final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand) */ { + public: + inline NSPanelMQTTManagerCommand_ThermostatTemperatureCommand() : NSPanelMQTTManagerCommand_ThermostatTemperatureCommand(nullptr) {} + ~NSPanelMQTTManagerCommand_ThermostatTemperatureCommand() override; + template + explicit PROTOBUF_CONSTEXPR NSPanelMQTTManagerCommand_ThermostatTemperatureCommand( + ::google::protobuf::internal::ConstantInitialized); + + inline NSPanelMQTTManagerCommand_ThermostatTemperatureCommand(const NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& from) : NSPanelMQTTManagerCommand_ThermostatTemperatureCommand(nullptr, from) {} + inline NSPanelMQTTManagerCommand_ThermostatTemperatureCommand(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand&& from) noexcept + : NSPanelMQTTManagerCommand_ThermostatTemperatureCommand(nullptr, std::move(from)) {} + inline NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& operator=(const NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& from) { + CopyFrom(from); + return *this; + } + inline NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& operator=(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand&& from) noexcept { + if (this == &from) return *this; + if (GetArena() == from.GetArena() +#ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetArena() != nullptr +#endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + static const ::google::protobuf::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::google::protobuf::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::google::protobuf::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& default_instance() { + return *internal_default_instance(); + } + static inline const NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* internal_default_instance() { + return reinterpret_cast( + &_NSPanelMQTTManagerCommand_ThermostatTemperatureCommand_default_instance_); + } + static constexpr int kIndexInFileMessages = 16; + friend void swap(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& a, NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& b) { a.Swap(&b); } + inline void Swap(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* other) { + if (other == this) return; +#ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetArena() != nullptr && GetArena() == other->GetArena()) { +#else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetArena() == other->GetArena()) { +#endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* New(::google::protobuf::Arena* arena = nullptr) const final { + return ::google::protobuf::Message::DefaultConstruct(arena); + } + using ::google::protobuf::Message::CopyFrom; + void CopyFrom(const NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& from); + using ::google::protobuf::Message::MergeFrom; + void MergeFrom(const NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& from) { NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::MergeImpl(*this, from); } + + private: + static void MergeImpl( + ::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() final; + ::size_t ByteSizeLong() const final; + ::uint8_t* _InternalSerialize( + ::uint8_t* target, + ::google::protobuf::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::google::protobuf::Arena* arena); + void SharedDtor(); + void InternalSwap(NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* other); + private: + friend class ::google::protobuf::internal::AnyMetadata; + static ::absl::string_view FullMessageName() { return "NSPanelMQTTManagerCommand.ThermostatTemperatureCommand"; } + + protected: + explicit NSPanelMQTTManagerCommand_ThermostatTemperatureCommand(::google::protobuf::Arena* arena); + NSPanelMQTTManagerCommand_ThermostatTemperatureCommand(::google::protobuf::Arena* arena, const NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& from); + NSPanelMQTTManagerCommand_ThermostatTemperatureCommand(::google::protobuf::Arena* arena, NSPanelMQTTManagerCommand_ThermostatTemperatureCommand&& from) noexcept + : NSPanelMQTTManagerCommand_ThermostatTemperatureCommand(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::Message::ClassData* GetClassData() const final; + + public: + ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kThermostatIdFieldNumber = 1, + kTemperatureFieldNumber = 2, + }; + // int32 thermostat_id = 1; + void clear_thermostat_id() ; + ::int32_t thermostat_id() const; + void set_thermostat_id(::int32_t value); + + private: + ::int32_t _internal_thermostat_id() const; + void _internal_set_thermostat_id(::int32_t value); + + public: + // float temperature = 2; + void clear_temperature() ; + float temperature() const; + void set_temperature(float value); + + private: + float _internal_temperature() const; + void _internal_set_temperature(float value); + + public: + // @@protoc_insertion_point(class_scope:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand) + private: + class _Internal; + friend class ::google::protobuf::internal::TcParser; + static const ::google::protobuf::internal::TcParseTable< + 1, 2, 0, + 0, 2> + _table_; + + static constexpr const void* _raw_default_instance_ = + &_NSPanelMQTTManagerCommand_ThermostatTemperatureCommand_default_instance_; + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_( + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena); + inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena, const Impl_& from, + const NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& from_msg); + ::int32_t thermostat_id_; + float temperature_; + mutable ::google::protobuf::internal::CachedSize _cached_size_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_protobuf_5fnspanel_2eproto; +}; +// ------------------------------------------------------------------- + +class NSPanelMQTTManagerCommand_ThermostatCommand final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:NSPanelMQTTManagerCommand.ThermostatCommand) */ { + public: + inline NSPanelMQTTManagerCommand_ThermostatCommand() : NSPanelMQTTManagerCommand_ThermostatCommand(nullptr) {} + ~NSPanelMQTTManagerCommand_ThermostatCommand() override; + template + explicit PROTOBUF_CONSTEXPR NSPanelMQTTManagerCommand_ThermostatCommand( + ::google::protobuf::internal::ConstantInitialized); + + inline NSPanelMQTTManagerCommand_ThermostatCommand(const NSPanelMQTTManagerCommand_ThermostatCommand& from) : NSPanelMQTTManagerCommand_ThermostatCommand(nullptr, from) {} + inline NSPanelMQTTManagerCommand_ThermostatCommand(NSPanelMQTTManagerCommand_ThermostatCommand&& from) noexcept + : NSPanelMQTTManagerCommand_ThermostatCommand(nullptr, std::move(from)) {} + inline NSPanelMQTTManagerCommand_ThermostatCommand& operator=(const NSPanelMQTTManagerCommand_ThermostatCommand& from) { + CopyFrom(from); + return *this; + } + inline NSPanelMQTTManagerCommand_ThermostatCommand& operator=(NSPanelMQTTManagerCommand_ThermostatCommand&& from) noexcept { + if (this == &from) return *this; + if (GetArena() == from.GetArena() +#ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetArena() != nullptr +#endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + static const ::google::protobuf::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::google::protobuf::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::google::protobuf::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const NSPanelMQTTManagerCommand_ThermostatCommand& default_instance() { + return *internal_default_instance(); + } + static inline const NSPanelMQTTManagerCommand_ThermostatCommand* internal_default_instance() { + return reinterpret_cast( + &_NSPanelMQTTManagerCommand_ThermostatCommand_default_instance_); + } + static constexpr int kIndexInFileMessages = 17; + friend void swap(NSPanelMQTTManagerCommand_ThermostatCommand& a, NSPanelMQTTManagerCommand_ThermostatCommand& b) { a.Swap(&b); } + inline void Swap(NSPanelMQTTManagerCommand_ThermostatCommand* other) { + if (other == this) return; +#ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetArena() != nullptr && GetArena() == other->GetArena()) { +#else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetArena() == other->GetArena()) { +#endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(NSPanelMQTTManagerCommand_ThermostatCommand* other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + NSPanelMQTTManagerCommand_ThermostatCommand* New(::google::protobuf::Arena* arena = nullptr) const final { + return ::google::protobuf::Message::DefaultConstruct(arena); + } + using ::google::protobuf::Message::CopyFrom; + void CopyFrom(const NSPanelMQTTManagerCommand_ThermostatCommand& from); + using ::google::protobuf::Message::MergeFrom; + void MergeFrom(const NSPanelMQTTManagerCommand_ThermostatCommand& from) { NSPanelMQTTManagerCommand_ThermostatCommand::MergeImpl(*this, from); } + + private: + static void MergeImpl( + ::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() final; + ::size_t ByteSizeLong() const final; + ::uint8_t* _InternalSerialize( + ::uint8_t* target, + ::google::protobuf::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::google::protobuf::Arena* arena); + void SharedDtor(); + void InternalSwap(NSPanelMQTTManagerCommand_ThermostatCommand* other); + private: + friend class ::google::protobuf::internal::AnyMetadata; + static ::absl::string_view FullMessageName() { return "NSPanelMQTTManagerCommand.ThermostatCommand"; } + + protected: + explicit NSPanelMQTTManagerCommand_ThermostatCommand(::google::protobuf::Arena* arena); + NSPanelMQTTManagerCommand_ThermostatCommand(::google::protobuf::Arena* arena, const NSPanelMQTTManagerCommand_ThermostatCommand& from); + NSPanelMQTTManagerCommand_ThermostatCommand(::google::protobuf::Arena* arena, NSPanelMQTTManagerCommand_ThermostatCommand&& from) noexcept + : NSPanelMQTTManagerCommand_ThermostatCommand(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::Message::ClassData* GetClassData() const final; + + public: + ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kOptionFieldNumber = 2, + kNewValueFieldNumber = 3, + kThermostatIdFieldNumber = 1, + }; + // string option = 2; + void clear_option() ; + const std::string& option() const; + template + void set_option(Arg_&& arg, Args_... args); + std::string* mutable_option(); + PROTOBUF_NODISCARD std::string* release_option(); + void set_allocated_option(std::string* value); + + private: + const std::string& _internal_option() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_option( + const std::string& value); + std::string* _internal_mutable_option(); + + public: + // string new_value = 3; + void clear_new_value() ; + const std::string& new_value() const; + template + void set_new_value(Arg_&& arg, Args_... args); + std::string* mutable_new_value(); + PROTOBUF_NODISCARD std::string* release_new_value(); + void set_allocated_new_value(std::string* value); + + private: + const std::string& _internal_new_value() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_new_value( + const std::string& value); + std::string* _internal_mutable_new_value(); + + public: + // int32 thermostat_id = 1; + void clear_thermostat_id() ; + ::int32_t thermostat_id() const; + void set_thermostat_id(::int32_t value); + + private: + ::int32_t _internal_thermostat_id() const; + void _internal_set_thermostat_id(::int32_t value); + + public: + // @@protoc_insertion_point(class_scope:NSPanelMQTTManagerCommand.ThermostatCommand) + private: + class _Internal; + friend class ::google::protobuf::internal::TcParser; + static const ::google::protobuf::internal::TcParseTable< + 2, 3, 0, + 67, 2> + _table_; + + static constexpr const void* _raw_default_instance_ = + &_NSPanelMQTTManagerCommand_ThermostatCommand_default_instance_; + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_( + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena); + inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena, const Impl_& from, + const NSPanelMQTTManagerCommand_ThermostatCommand& from_msg); + ::google::protobuf::internal::ArenaStringPtr option_; + ::google::protobuf::internal::ArenaStringPtr new_value_; + ::int32_t thermostat_id_; + mutable ::google::protobuf::internal::CachedSize _cached_size_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_protobuf_5fnspanel_2eproto; +}; +// ------------------------------------------------------------------- + class NSPanelMQTTManagerCommand_SaveSceneCommand final : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition:NSPanelMQTTManagerCommand.SaveSceneCommand) */ { public: @@ -3551,6 +3949,10 @@ class NSPanelStatusReport final : public ::google::protobuf::Message kRssiFieldNumber = 3, kHeapUsedPctFieldNumber = 4, kTemperatureFieldNumber = 6, + kHumidityFieldNumber = 13, + kHasHumidityFieldNumber = 12, + kHasPressureFieldNumber = 14, + kPressureFieldNumber = 15, }; // repeated .NSPanelWarning warnings = 8; int warnings_size() const; @@ -3698,13 +4100,53 @@ class NSPanelStatusReport final : public ::google::protobuf::Message float _internal_temperature() const; void _internal_set_temperature(float value); + public: + // float humidity = 13; + void clear_humidity() ; + float humidity() const; + void set_humidity(float value); + + private: + float _internal_humidity() const; + void _internal_set_humidity(float value); + + public: + // bool has_humidity = 12; + void clear_has_humidity() ; + bool has_humidity() const; + void set_has_humidity(bool value); + + private: + bool _internal_has_humidity() const; + void _internal_set_has_humidity(bool value); + + public: + // bool has_pressure = 14; + void clear_has_pressure() ; + bool has_pressure() const; + void set_has_pressure(bool value); + + private: + bool _internal_has_pressure() const; + void _internal_set_has_pressure(bool value); + + public: + // float pressure = 15; + void clear_pressure() ; + float pressure() const; + void set_pressure(float value); + + private: + float _internal_pressure() const; + void _internal_set_pressure(float value); + public: // @@protoc_insertion_point(class_scope:NSPanelStatusReport) private: class _Internal; friend class ::google::protobuf::internal::TcParser; static const ::google::protobuf::internal::TcParseTable< - 4, 11, 1, + 4, 15, 1, 92, 2> _table_; @@ -3736,6 +4178,10 @@ class NSPanelStatusReport final : public ::google::protobuf::Message ::int32_t rssi_; ::int32_t heap_used_pct_; float temperature_; + float humidity_; + bool has_humidity_; + bool has_pressure_; + float pressure_; mutable ::google::protobuf::internal::CachedSize _cached_size_; PROTOBUF_TSAN_DECLARE_MEMBER }; @@ -4023,13 +4469,15 @@ class NSPanelMQTTManagerCommand final : public ::google::protobuf::Message kToggleEntityFromEntitiesPage = 4, kSaveSceneCommand = 5, kButtonPressed = 6, + kThermostatTemperatureCommand = 7, + kThermostatCommand = 8, COMMANDDATA_NOT_SET = 0, }; static inline const NSPanelMQTTManagerCommand* internal_default_instance() { return reinterpret_cast( &_NSPanelMQTTManagerCommand_default_instance_); } - static constexpr int kIndexInFileMessages = 16; + static constexpr int kIndexInFileMessages = 18; friend void swap(NSPanelMQTTManagerCommand& a, NSPanelMQTTManagerCommand& b) { a.Swap(&b); } inline void Swap(NSPanelMQTTManagerCommand* other) { if (other == this) return; @@ -4101,6 +4549,8 @@ class NSPanelMQTTManagerCommand final : public ::google::protobuf::Message using ToggleEntityFromEntitiesPage = NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPage; using SaveSceneCommand = NSPanelMQTTManagerCommand_SaveSceneCommand; using ButtonPressed = NSPanelMQTTManagerCommand_ButtonPressed; + using ThermostatTemperatureCommand = NSPanelMQTTManagerCommand_ThermostatTemperatureCommand; + using ThermostatCommand = NSPanelMQTTManagerCommand_ThermostatCommand; using AffectLightsOptions = NSPanelMQTTManagerCommand_AffectLightsOptions; static constexpr AffectLightsOptions ALL = NSPanelMQTTManagerCommand_AffectLightsOptions_ALL; static constexpr AffectLightsOptions TABLE_LIGHTS = NSPanelMQTTManagerCommand_AffectLightsOptions_TABLE_LIGHTS; @@ -4131,6 +4581,8 @@ class NSPanelMQTTManagerCommand final : public ::google::protobuf::Message kToggleEntityFromEntitiesPageFieldNumber = 4, kSaveSceneCommandFieldNumber = 5, kButtonPressedFieldNumber = 6, + kThermostatTemperatureCommandFieldNumber = 7, + kThermostatCommandFieldNumber = 8, }; // int32 nspanel_id = 100; void clear_nspanel_id() ; @@ -4252,8 +4704,46 @@ class NSPanelMQTTManagerCommand final : public ::google::protobuf::Message ::NSPanelMQTTManagerCommand_ButtonPressed* unsafe_arena_release_button_pressed(); private: - const ::NSPanelMQTTManagerCommand_ButtonPressed& _internal_button_pressed() const; - ::NSPanelMQTTManagerCommand_ButtonPressed* _internal_mutable_button_pressed(); + const ::NSPanelMQTTManagerCommand_ButtonPressed& _internal_button_pressed() const; + ::NSPanelMQTTManagerCommand_ButtonPressed* _internal_mutable_button_pressed(); + + public: + // .NSPanelMQTTManagerCommand.ThermostatTemperatureCommand thermostat_temperature_command = 7; + bool has_thermostat_temperature_command() const; + private: + bool _internal_has_thermostat_temperature_command() const; + + public: + void clear_thermostat_temperature_command() ; + const ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& thermostat_temperature_command() const; + PROTOBUF_NODISCARD ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* release_thermostat_temperature_command(); + ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* mutable_thermostat_temperature_command(); + void set_allocated_thermostat_temperature_command(::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* value); + void unsafe_arena_set_allocated_thermostat_temperature_command(::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* value); + ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* unsafe_arena_release_thermostat_temperature_command(); + + private: + const ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& _internal_thermostat_temperature_command() const; + ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* _internal_mutable_thermostat_temperature_command(); + + public: + // .NSPanelMQTTManagerCommand.ThermostatCommand thermostat_command = 8; + bool has_thermostat_command() const; + private: + bool _internal_has_thermostat_command() const; + + public: + void clear_thermostat_command() ; + const ::NSPanelMQTTManagerCommand_ThermostatCommand& thermostat_command() const; + PROTOBUF_NODISCARD ::NSPanelMQTTManagerCommand_ThermostatCommand* release_thermostat_command(); + ::NSPanelMQTTManagerCommand_ThermostatCommand* mutable_thermostat_command(); + void set_allocated_thermostat_command(::NSPanelMQTTManagerCommand_ThermostatCommand* value); + void unsafe_arena_set_allocated_thermostat_command(::NSPanelMQTTManagerCommand_ThermostatCommand* value); + ::NSPanelMQTTManagerCommand_ThermostatCommand* unsafe_arena_release_thermostat_command(); + + private: + const ::NSPanelMQTTManagerCommand_ThermostatCommand& _internal_thermostat_command() const; + ::NSPanelMQTTManagerCommand_ThermostatCommand* _internal_mutable_thermostat_command(); public: void clear_CommandData(); @@ -4267,11 +4757,13 @@ class NSPanelMQTTManagerCommand final : public ::google::protobuf::Message void set_has_toggle_entity_from_entities_page(); void set_has_save_scene_command(); void set_has_button_pressed(); + void set_has_thermostat_temperature_command(); + void set_has_thermostat_command(); inline bool has_CommandData() const; inline void clear_has_CommandData(); friend class ::google::protobuf::internal::TcParser; static const ::google::protobuf::internal::TcParseTable< - 0, 7, 6, + 0, 9, 8, 0, 7> _table_; @@ -4302,6 +4794,8 @@ class NSPanelMQTTManagerCommand final : public ::google::protobuf::Message ::NSPanelMQTTManagerCommand_ToggleEntityFromEntitiesPage* toggle_entity_from_entities_page_; ::NSPanelMQTTManagerCommand_SaveSceneCommand* save_scene_command_; ::NSPanelMQTTManagerCommand_ButtonPressed* button_pressed_; + ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* thermostat_temperature_command_; + ::NSPanelMQTTManagerCommand_ThermostatCommand* thermostat_command_; } CommandData_; mutable ::google::protobuf::internal::CachedSize _cached_size_; ::uint32_t _oneof_case_[1]; @@ -4480,6 +4974,8 @@ class NSPanelConfig final : public ::google::protobuf::Message static constexpr NSPanelButtonMode DIRECT = NSPanelConfig_NSPanelButtonMode_DIRECT; static constexpr NSPanelButtonMode FOLLOW = NSPanelConfig_NSPanelButtonMode_FOLLOW; static constexpr NSPanelButtonMode NOTIFY_MANAGER = NSPanelConfig_NSPanelButtonMode_NOTIFY_MANAGER; + static constexpr NSPanelButtonMode THERMOSTAT_HEAT = NSPanelConfig_NSPanelButtonMode_THERMOSTAT_HEAT; + static constexpr NSPanelButtonMode THERMOSTAT_COOL = NSPanelConfig_NSPanelButtonMode_THERMOSTAT_COOL; static inline bool NSPanelButtonMode_IsValid(int value) { return NSPanelConfig_NSPanelButtonMode_IsValid(value); } @@ -4531,6 +5027,10 @@ class NSPanelConfig final : public ::google::protobuf::Message kOptimisticModeFieldNumber = 31, kLockedToDefaultRoomFieldNumber = 39, kDefaultLightBrightessFieldNumber = 38, + kButton1LowerTemperatureFieldNumber = 41, + kButton1UpperTemperatureFieldNumber = 42, + kButton2LowerTemperatureFieldNumber = 43, + kButton2UpperTemperatureFieldNumber = 44, }; // repeated .NSPanelConfig.RoomInfo room_infos = 17; int room_infos_size() const; @@ -4894,13 +5394,53 @@ class NSPanelConfig final : public ::google::protobuf::Message ::int32_t _internal_default_light_brightess() const; void _internal_set_default_light_brightess(::int32_t value); + public: + // int32 button1_lower_temperature = 41; + void clear_button1_lower_temperature() ; + ::int32_t button1_lower_temperature() const; + void set_button1_lower_temperature(::int32_t value); + + private: + ::int32_t _internal_button1_lower_temperature() const; + void _internal_set_button1_lower_temperature(::int32_t value); + + public: + // int32 button1_upper_temperature = 42; + void clear_button1_upper_temperature() ; + ::int32_t button1_upper_temperature() const; + void set_button1_upper_temperature(::int32_t value); + + private: + ::int32_t _internal_button1_upper_temperature() const; + void _internal_set_button1_upper_temperature(::int32_t value); + + public: + // int32 button2_lower_temperature = 43; + void clear_button2_lower_temperature() ; + ::int32_t button2_lower_temperature() const; + void set_button2_lower_temperature(::int32_t value); + + private: + ::int32_t _internal_button2_lower_temperature() const; + void _internal_set_button2_lower_temperature(::int32_t value); + + public: + // int32 button2_upper_temperature = 44; + void clear_button2_upper_temperature() ; + ::int32_t button2_upper_temperature() const; + void set_button2_upper_temperature(::int32_t value); + + private: + ::int32_t _internal_button2_upper_temperature() const; + void _internal_set_button2_upper_temperature(::int32_t value); + public: // @@protoc_insertion_point(class_scope:NSPanelConfig) private: class _Internal; friend class ::google::protobuf::internal::TcParser; static const ::google::protobuf::internal::TcParseTable< - 5, 32, 1, + 5, 36, 1, 94, 7> _table_; @@ -4956,6 +5496,10 @@ class NSPanelConfig final : public ::google::protobuf::Message bool optimistic_mode_; bool locked_to_default_room_; ::int32_t default_light_brightess_; + ::int32_t button1_lower_temperature_; + ::int32_t button1_upper_temperature_; + ::int32_t button2_lower_temperature_; + ::int32_t button2_upper_temperature_; mutable ::google::protobuf::internal::CachedSize _cached_size_; PROTOBUF_TSAN_DECLARE_MEMBER }; @@ -5951,6 +6495,94 @@ inline void NSPanelConfig::set_allocated_inside_temperature_sensor_mqtt_topic(st // @@protoc_insertion_point(field_set_allocated:NSPanelConfig.inside_temperature_sensor_mqtt_topic) } +// int32 button1_lower_temperature = 41; +inline void NSPanelConfig::clear_button1_lower_temperature() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.button1_lower_temperature_ = 0; +} +inline ::int32_t NSPanelConfig::button1_lower_temperature() const { + // @@protoc_insertion_point(field_get:NSPanelConfig.button1_lower_temperature) + return _internal_button1_lower_temperature(); +} +inline void NSPanelConfig::set_button1_lower_temperature(::int32_t value) { + _internal_set_button1_lower_temperature(value); + // @@protoc_insertion_point(field_set:NSPanelConfig.button1_lower_temperature) +} +inline ::int32_t NSPanelConfig::_internal_button1_lower_temperature() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.button1_lower_temperature_; +} +inline void NSPanelConfig::_internal_set_button1_lower_temperature(::int32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.button1_lower_temperature_ = value; +} + +// int32 button1_upper_temperature = 42; +inline void NSPanelConfig::clear_button1_upper_temperature() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.button1_upper_temperature_ = 0; +} +inline ::int32_t NSPanelConfig::button1_upper_temperature() const { + // @@protoc_insertion_point(field_get:NSPanelConfig.button1_upper_temperature) + return _internal_button1_upper_temperature(); +} +inline void NSPanelConfig::set_button1_upper_temperature(::int32_t value) { + _internal_set_button1_upper_temperature(value); + // @@protoc_insertion_point(field_set:NSPanelConfig.button1_upper_temperature) +} +inline ::int32_t NSPanelConfig::_internal_button1_upper_temperature() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.button1_upper_temperature_; +} +inline void NSPanelConfig::_internal_set_button1_upper_temperature(::int32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.button1_upper_temperature_ = value; +} + +// int32 button2_lower_temperature = 43; +inline void NSPanelConfig::clear_button2_lower_temperature() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.button2_lower_temperature_ = 0; +} +inline ::int32_t NSPanelConfig::button2_lower_temperature() const { + // @@protoc_insertion_point(field_get:NSPanelConfig.button2_lower_temperature) + return _internal_button2_lower_temperature(); +} +inline void NSPanelConfig::set_button2_lower_temperature(::int32_t value) { + _internal_set_button2_lower_temperature(value); + // @@protoc_insertion_point(field_set:NSPanelConfig.button2_lower_temperature) +} +inline ::int32_t NSPanelConfig::_internal_button2_lower_temperature() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.button2_lower_temperature_; +} +inline void NSPanelConfig::_internal_set_button2_lower_temperature(::int32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.button2_lower_temperature_ = value; +} + +// int32 button2_upper_temperature = 44; +inline void NSPanelConfig::clear_button2_upper_temperature() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.button2_upper_temperature_ = 0; +} +inline ::int32_t NSPanelConfig::button2_upper_temperature() const { + // @@protoc_insertion_point(field_get:NSPanelConfig.button2_upper_temperature) + return _internal_button2_upper_temperature(); +} +inline void NSPanelConfig::set_button2_upper_temperature(::int32_t value) { + _internal_set_button2_upper_temperature(value); + // @@protoc_insertion_point(field_set:NSPanelConfig.button2_upper_temperature) +} +inline ::int32_t NSPanelConfig::_internal_button2_upper_temperature() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.button2_upper_temperature_; +} +inline void NSPanelConfig::_internal_set_button2_upper_temperature(::int32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.button2_upper_temperature_ = value; +} + // ------------------------------------------------------------------- // NSPanelWarning @@ -6440,6 +7072,94 @@ inline void NSPanelStatusReport::set_allocated_md5_tft_gui(std::string* value) { // @@protoc_insertion_point(field_set_allocated:NSPanelStatusReport.md5_tft_gui) } +// bool has_humidity = 12; +inline void NSPanelStatusReport::clear_has_humidity() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.has_humidity_ = false; +} +inline bool NSPanelStatusReport::has_humidity() const { + // @@protoc_insertion_point(field_get:NSPanelStatusReport.has_humidity) + return _internal_has_humidity(); +} +inline void NSPanelStatusReport::set_has_humidity(bool value) { + _internal_set_has_humidity(value); + // @@protoc_insertion_point(field_set:NSPanelStatusReport.has_humidity) +} +inline bool NSPanelStatusReport::_internal_has_humidity() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.has_humidity_; +} +inline void NSPanelStatusReport::_internal_set_has_humidity(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.has_humidity_ = value; +} + +// float humidity = 13; +inline void NSPanelStatusReport::clear_humidity() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.humidity_ = 0; +} +inline float NSPanelStatusReport::humidity() const { + // @@protoc_insertion_point(field_get:NSPanelStatusReport.humidity) + return _internal_humidity(); +} +inline void NSPanelStatusReport::set_humidity(float value) { + _internal_set_humidity(value); + // @@protoc_insertion_point(field_set:NSPanelStatusReport.humidity) +} +inline float NSPanelStatusReport::_internal_humidity() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.humidity_; +} +inline void NSPanelStatusReport::_internal_set_humidity(float value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.humidity_ = value; +} + +// bool has_pressure = 14; +inline void NSPanelStatusReport::clear_has_pressure() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.has_pressure_ = false; +} +inline bool NSPanelStatusReport::has_pressure() const { + // @@protoc_insertion_point(field_get:NSPanelStatusReport.has_pressure) + return _internal_has_pressure(); +} +inline void NSPanelStatusReport::set_has_pressure(bool value) { + _internal_set_has_pressure(value); + // @@protoc_insertion_point(field_set:NSPanelStatusReport.has_pressure) +} +inline bool NSPanelStatusReport::_internal_has_pressure() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.has_pressure_; +} +inline void NSPanelStatusReport::_internal_set_has_pressure(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.has_pressure_ = value; +} + +// float pressure = 15; +inline void NSPanelStatusReport::clear_pressure() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.pressure_ = 0; +} +inline float NSPanelStatusReport::pressure() const { + // @@protoc_insertion_point(field_get:NSPanelStatusReport.pressure) + return _internal_pressure(); +} +inline void NSPanelStatusReport::set_pressure(float value) { + _internal_set_pressure(value); + // @@protoc_insertion_point(field_set:NSPanelStatusReport.pressure) +} +inline float NSPanelStatusReport::_internal_pressure() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.pressure_; +} +inline void NSPanelStatusReport::_internal_set_pressure(float value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.pressure_ = value; +} + // ------------------------------------------------------------------- // NSPanelLightStatus @@ -8634,6 +9354,180 @@ inline void NSPanelMQTTManagerCommand_ButtonPressed::_internal_set_button_id(::i // ------------------------------------------------------------------- +// NSPanelMQTTManagerCommand_ThermostatTemperatureCommand + +// int32 thermostat_id = 1; +inline void NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::clear_thermostat_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.thermostat_id_ = 0; +} +inline ::int32_t NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::thermostat_id() const { + // @@protoc_insertion_point(field_get:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand.thermostat_id) + return _internal_thermostat_id(); +} +inline void NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::set_thermostat_id(::int32_t value) { + _internal_set_thermostat_id(value); + // @@protoc_insertion_point(field_set:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand.thermostat_id) +} +inline ::int32_t NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::_internal_thermostat_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.thermostat_id_; +} +inline void NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::_internal_set_thermostat_id(::int32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.thermostat_id_ = value; +} + +// float temperature = 2; +inline void NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::clear_temperature() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.temperature_ = 0; +} +inline float NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::temperature() const { + // @@protoc_insertion_point(field_get:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand.temperature) + return _internal_temperature(); +} +inline void NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::set_temperature(float value) { + _internal_set_temperature(value); + // @@protoc_insertion_point(field_set:NSPanelMQTTManagerCommand.ThermostatTemperatureCommand.temperature) +} +inline float NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::_internal_temperature() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.temperature_; +} +inline void NSPanelMQTTManagerCommand_ThermostatTemperatureCommand::_internal_set_temperature(float value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.temperature_ = value; +} + +// ------------------------------------------------------------------- + +// NSPanelMQTTManagerCommand_ThermostatCommand + +// int32 thermostat_id = 1; +inline void NSPanelMQTTManagerCommand_ThermostatCommand::clear_thermostat_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.thermostat_id_ = 0; +} +inline ::int32_t NSPanelMQTTManagerCommand_ThermostatCommand::thermostat_id() const { + // @@protoc_insertion_point(field_get:NSPanelMQTTManagerCommand.ThermostatCommand.thermostat_id) + return _internal_thermostat_id(); +} +inline void NSPanelMQTTManagerCommand_ThermostatCommand::set_thermostat_id(::int32_t value) { + _internal_set_thermostat_id(value); + // @@protoc_insertion_point(field_set:NSPanelMQTTManagerCommand.ThermostatCommand.thermostat_id) +} +inline ::int32_t NSPanelMQTTManagerCommand_ThermostatCommand::_internal_thermostat_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.thermostat_id_; +} +inline void NSPanelMQTTManagerCommand_ThermostatCommand::_internal_set_thermostat_id(::int32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.thermostat_id_ = value; +} + +// string option = 2; +inline void NSPanelMQTTManagerCommand_ThermostatCommand::clear_option() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.option_.ClearToEmpty(); +} +inline const std::string& NSPanelMQTTManagerCommand_ThermostatCommand::option() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelMQTTManagerCommand.ThermostatCommand.option) + return _internal_option(); +} +template +inline PROTOBUF_ALWAYS_INLINE void NSPanelMQTTManagerCommand_ThermostatCommand::set_option(Arg_&& arg, + Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.option_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:NSPanelMQTTManagerCommand.ThermostatCommand.option) +} +inline std::string* NSPanelMQTTManagerCommand_ThermostatCommand::mutable_option() ABSL_ATTRIBUTE_LIFETIME_BOUND { + std::string* _s = _internal_mutable_option(); + // @@protoc_insertion_point(field_mutable:NSPanelMQTTManagerCommand.ThermostatCommand.option) + return _s; +} +inline const std::string& NSPanelMQTTManagerCommand_ThermostatCommand::_internal_option() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.option_.Get(); +} +inline void NSPanelMQTTManagerCommand_ThermostatCommand::_internal_set_option(const std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.option_.Set(value, GetArena()); +} +inline std::string* NSPanelMQTTManagerCommand_ThermostatCommand::_internal_mutable_option() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.option_.Mutable( GetArena()); +} +inline std::string* NSPanelMQTTManagerCommand_ThermostatCommand::release_option() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:NSPanelMQTTManagerCommand.ThermostatCommand.option) + return _impl_.option_.Release(); +} +inline void NSPanelMQTTManagerCommand_ThermostatCommand::set_allocated_option(std::string* value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.option_.SetAllocated(value, GetArena()); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.option_.IsDefault()) { + _impl_.option_.Set("", GetArena()); + } + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.ThermostatCommand.option) +} + +// string new_value = 3; +inline void NSPanelMQTTManagerCommand_ThermostatCommand::clear_new_value() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.new_value_.ClearToEmpty(); +} +inline const std::string& NSPanelMQTTManagerCommand_ThermostatCommand::new_value() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelMQTTManagerCommand.ThermostatCommand.new_value) + return _internal_new_value(); +} +template +inline PROTOBUF_ALWAYS_INLINE void NSPanelMQTTManagerCommand_ThermostatCommand::set_new_value(Arg_&& arg, + Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.new_value_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:NSPanelMQTTManagerCommand.ThermostatCommand.new_value) +} +inline std::string* NSPanelMQTTManagerCommand_ThermostatCommand::mutable_new_value() ABSL_ATTRIBUTE_LIFETIME_BOUND { + std::string* _s = _internal_mutable_new_value(); + // @@protoc_insertion_point(field_mutable:NSPanelMQTTManagerCommand.ThermostatCommand.new_value) + return _s; +} +inline const std::string& NSPanelMQTTManagerCommand_ThermostatCommand::_internal_new_value() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.new_value_.Get(); +} +inline void NSPanelMQTTManagerCommand_ThermostatCommand::_internal_set_new_value(const std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.new_value_.Set(value, GetArena()); +} +inline std::string* NSPanelMQTTManagerCommand_ThermostatCommand::_internal_mutable_new_value() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.new_value_.Mutable( GetArena()); +} +inline std::string* NSPanelMQTTManagerCommand_ThermostatCommand::release_new_value() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:NSPanelMQTTManagerCommand.ThermostatCommand.new_value) + return _impl_.new_value_.Release(); +} +inline void NSPanelMQTTManagerCommand_ThermostatCommand::set_allocated_new_value(std::string* value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.new_value_.SetAllocated(value, GetArena()); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.new_value_.IsDefault()) { + _impl_.new_value_.Set("", GetArena()); + } + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:NSPanelMQTTManagerCommand.ThermostatCommand.new_value) +} + +// ------------------------------------------------------------------- + // NSPanelMQTTManagerCommand // .NSPanelMQTTManagerCommand.FirstPageTurnLightOn first_page_turn_on = 1; @@ -9110,6 +10004,164 @@ inline ::NSPanelMQTTManagerCommand_ButtonPressed* NSPanelMQTTManagerCommand::mut return _msg; } +// .NSPanelMQTTManagerCommand.ThermostatTemperatureCommand thermostat_temperature_command = 7; +inline bool NSPanelMQTTManagerCommand::has_thermostat_temperature_command() const { + return CommandData_case() == kThermostatTemperatureCommand; +} +inline bool NSPanelMQTTManagerCommand::_internal_has_thermostat_temperature_command() const { + return CommandData_case() == kThermostatTemperatureCommand; +} +inline void NSPanelMQTTManagerCommand::set_has_thermostat_temperature_command() { + _impl_._oneof_case_[0] = kThermostatTemperatureCommand; +} +inline void NSPanelMQTTManagerCommand::clear_thermostat_temperature_command() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (CommandData_case() == kThermostatTemperatureCommand) { + if (GetArena() == nullptr) { + delete _impl_.CommandData_.thermostat_temperature_command_; + } else if (::google::protobuf::internal::DebugHardenClearOneofMessageOnArena()) { + ::google::protobuf::internal::MaybePoisonAfterClear(_impl_.CommandData_.thermostat_temperature_command_); + } + clear_has_CommandData(); + } +} +inline ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* NSPanelMQTTManagerCommand::release_thermostat_temperature_command() { + // @@protoc_insertion_point(field_release:NSPanelMQTTManagerCommand.thermostat_temperature_command) + if (CommandData_case() == kThermostatTemperatureCommand) { + clear_has_CommandData(); + auto* temp = _impl_.CommandData_.thermostat_temperature_command_; + if (GetArena() != nullptr) { + temp = ::google::protobuf::internal::DuplicateIfNonNull(temp); + } + _impl_.CommandData_.thermostat_temperature_command_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& NSPanelMQTTManagerCommand::_internal_thermostat_temperature_command() const { + return CommandData_case() == kThermostatTemperatureCommand ? *_impl_.CommandData_.thermostat_temperature_command_ : reinterpret_cast<::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand&>(::_NSPanelMQTTManagerCommand_ThermostatTemperatureCommand_default_instance_); +} +inline const ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand& NSPanelMQTTManagerCommand::thermostat_temperature_command() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelMQTTManagerCommand.thermostat_temperature_command) + return _internal_thermostat_temperature_command(); +} +inline ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* NSPanelMQTTManagerCommand::unsafe_arena_release_thermostat_temperature_command() { + // @@protoc_insertion_point(field_unsafe_arena_release:NSPanelMQTTManagerCommand.thermostat_temperature_command) + if (CommandData_case() == kThermostatTemperatureCommand) { + clear_has_CommandData(); + auto* temp = _impl_.CommandData_.thermostat_temperature_command_; + _impl_.CommandData_.thermostat_temperature_command_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void NSPanelMQTTManagerCommand::unsafe_arena_set_allocated_thermostat_temperature_command(::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* value) { + // We rely on the oneof clear method to free the earlier contents + // of this oneof. We can directly use the pointer we're given to + // set the new value. + clear_CommandData(); + if (value) { + set_has_thermostat_temperature_command(); + _impl_.CommandData_.thermostat_temperature_command_ = value; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:NSPanelMQTTManagerCommand.thermostat_temperature_command) +} +inline ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* NSPanelMQTTManagerCommand::_internal_mutable_thermostat_temperature_command() { + if (CommandData_case() != kThermostatTemperatureCommand) { + clear_CommandData(); + set_has_thermostat_temperature_command(); + _impl_.CommandData_.thermostat_temperature_command_ = + ::google::protobuf::Message::DefaultConstruct<::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand>(GetArena()); + } + return _impl_.CommandData_.thermostat_temperature_command_; +} +inline ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* NSPanelMQTTManagerCommand::mutable_thermostat_temperature_command() ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::NSPanelMQTTManagerCommand_ThermostatTemperatureCommand* _msg = _internal_mutable_thermostat_temperature_command(); + // @@protoc_insertion_point(field_mutable:NSPanelMQTTManagerCommand.thermostat_temperature_command) + return _msg; +} + +// .NSPanelMQTTManagerCommand.ThermostatCommand thermostat_command = 8; +inline bool NSPanelMQTTManagerCommand::has_thermostat_command() const { + return CommandData_case() == kThermostatCommand; +} +inline bool NSPanelMQTTManagerCommand::_internal_has_thermostat_command() const { + return CommandData_case() == kThermostatCommand; +} +inline void NSPanelMQTTManagerCommand::set_has_thermostat_command() { + _impl_._oneof_case_[0] = kThermostatCommand; +} +inline void NSPanelMQTTManagerCommand::clear_thermostat_command() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (CommandData_case() == kThermostatCommand) { + if (GetArena() == nullptr) { + delete _impl_.CommandData_.thermostat_command_; + } else if (::google::protobuf::internal::DebugHardenClearOneofMessageOnArena()) { + ::google::protobuf::internal::MaybePoisonAfterClear(_impl_.CommandData_.thermostat_command_); + } + clear_has_CommandData(); + } +} +inline ::NSPanelMQTTManagerCommand_ThermostatCommand* NSPanelMQTTManagerCommand::release_thermostat_command() { + // @@protoc_insertion_point(field_release:NSPanelMQTTManagerCommand.thermostat_command) + if (CommandData_case() == kThermostatCommand) { + clear_has_CommandData(); + auto* temp = _impl_.CommandData_.thermostat_command_; + if (GetArena() != nullptr) { + temp = ::google::protobuf::internal::DuplicateIfNonNull(temp); + } + _impl_.CommandData_.thermostat_command_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::NSPanelMQTTManagerCommand_ThermostatCommand& NSPanelMQTTManagerCommand::_internal_thermostat_command() const { + return CommandData_case() == kThermostatCommand ? *_impl_.CommandData_.thermostat_command_ : reinterpret_cast<::NSPanelMQTTManagerCommand_ThermostatCommand&>(::_NSPanelMQTTManagerCommand_ThermostatCommand_default_instance_); +} +inline const ::NSPanelMQTTManagerCommand_ThermostatCommand& NSPanelMQTTManagerCommand::thermostat_command() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelMQTTManagerCommand.thermostat_command) + return _internal_thermostat_command(); +} +inline ::NSPanelMQTTManagerCommand_ThermostatCommand* NSPanelMQTTManagerCommand::unsafe_arena_release_thermostat_command() { + // @@protoc_insertion_point(field_unsafe_arena_release:NSPanelMQTTManagerCommand.thermostat_command) + if (CommandData_case() == kThermostatCommand) { + clear_has_CommandData(); + auto* temp = _impl_.CommandData_.thermostat_command_; + _impl_.CommandData_.thermostat_command_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void NSPanelMQTTManagerCommand::unsafe_arena_set_allocated_thermostat_command(::NSPanelMQTTManagerCommand_ThermostatCommand* value) { + // We rely on the oneof clear method to free the earlier contents + // of this oneof. We can directly use the pointer we're given to + // set the new value. + clear_CommandData(); + if (value) { + set_has_thermostat_command(); + _impl_.CommandData_.thermostat_command_ = value; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:NSPanelMQTTManagerCommand.thermostat_command) +} +inline ::NSPanelMQTTManagerCommand_ThermostatCommand* NSPanelMQTTManagerCommand::_internal_mutable_thermostat_command() { + if (CommandData_case() != kThermostatCommand) { + clear_CommandData(); + set_has_thermostat_command(); + _impl_.CommandData_.thermostat_command_ = + ::google::protobuf::Message::DefaultConstruct<::NSPanelMQTTManagerCommand_ThermostatCommand>(GetArena()); + } + return _impl_.CommandData_.thermostat_command_; +} +inline ::NSPanelMQTTManagerCommand_ThermostatCommand* NSPanelMQTTManagerCommand::mutable_thermostat_command() ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::NSPanelMQTTManagerCommand_ThermostatCommand* _msg = _internal_mutable_thermostat_command(); + // @@protoc_insertion_point(field_mutable:NSPanelMQTTManagerCommand.thermostat_command) + return _msg; +} + // int32 nspanel_id = 100; inline void NSPanelMQTTManagerCommand::clear_nspanel_id() { ::google::protobuf::internal::TSanWrite(&_impl_); diff --git a/docker/MQTTManager/include/protobuf/protobuf_nspanel_entity.pb.cc b/docker/MQTTManager/include/protobuf/protobuf_nspanel_entity.pb.cc index d788e371..6e126a8e 100644 --- a/docker/MQTTManager/include/protobuf/protobuf_nspanel_entity.pb.cc +++ b/docker/MQTTManager/include/protobuf/protobuf_nspanel_entity.pb.cc @@ -24,6 +24,30 @@ namespace _pb = ::google::protobuf; namespace _pbi = ::google::protobuf::internal; namespace _fl = ::google::protobuf::internal::field_layout; +inline constexpr NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::Impl_::Impl_( + ::_pbi::ConstantInitialized) noexcept + : value_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + icon_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + _cached_size_{0} {} + +template +PROTOBUF_CONSTEXPR NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue(::_pbi::ConstantInitialized) + : _impl_(::_pbi::ConstantInitialized()) {} +struct NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValueDefaultTypeInternal { + PROTOBUF_CONSTEXPR NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValueDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {} + ~NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValueDefaultTypeInternal() {} + union { + NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue _instance; + }; +}; + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValueDefaultTypeInternal _NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue_default_instance_; + inline constexpr NSPanelEntityState_Light::Impl_::Impl_( ::_pbi::ConstantInitialized) noexcept : name_( @@ -53,6 +77,61 @@ struct NSPanelEntityState_LightDefaultTypeInternal { PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 NSPanelEntityState_LightDefaultTypeInternal _NSPanelEntityState_Light_default_instance_; +inline constexpr NSPanelEntityState_Thermostat_ThermostatOption::Impl_::Impl_( + ::_pbi::ConstantInitialized) noexcept + : options_{}, + name_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + current_value_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + current_icon_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + _cached_size_{0} {} + +template +PROTOBUF_CONSTEXPR NSPanelEntityState_Thermostat_ThermostatOption::NSPanelEntityState_Thermostat_ThermostatOption(::_pbi::ConstantInitialized) + : _impl_(::_pbi::ConstantInitialized()) {} +struct NSPanelEntityState_Thermostat_ThermostatOptionDefaultTypeInternal { + PROTOBUF_CONSTEXPR NSPanelEntityState_Thermostat_ThermostatOptionDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {} + ~NSPanelEntityState_Thermostat_ThermostatOptionDefaultTypeInternal() {} + union { + NSPanelEntityState_Thermostat_ThermostatOption _instance; + }; +}; + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 NSPanelEntityState_Thermostat_ThermostatOptionDefaultTypeInternal _NSPanelEntityState_Thermostat_ThermostatOption_default_instance_; + +inline constexpr NSPanelEntityState_Thermostat::Impl_::Impl_( + ::_pbi::ConstantInitialized) noexcept + : options_{}, + name_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + thermostat_id_{0}, + current_temperature_{0}, + has_current_temperature_{false}, + set_temperature_{0}, + step_size_{0}, + _cached_size_{0} {} + +template +PROTOBUF_CONSTEXPR NSPanelEntityState_Thermostat::NSPanelEntityState_Thermostat(::_pbi::ConstantInitialized) + : _impl_(::_pbi::ConstantInitialized()) {} +struct NSPanelEntityState_ThermostatDefaultTypeInternal { + PROTOBUF_CONSTEXPR NSPanelEntityState_ThermostatDefaultTypeInternal() : _instance(::_pbi::ConstantInitialized{}) {} + ~NSPanelEntityState_ThermostatDefaultTypeInternal() {} + union { + NSPanelEntityState_Thermostat _instance; + }; +}; + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 NSPanelEntityState_ThermostatDefaultTypeInternal _NSPanelEntityState_Thermostat_default_instance_; + inline constexpr NSPanelEntityState::Impl_::Impl_( ::_pbi::ConstantInitialized) noexcept : entity_{}, @@ -96,6 +175,43 @@ const ::uint32_t PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Light, _impl_.can_color_), PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Light, _impl_.current_light_mode_), ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + ~0u, // no _split_ + ~0u, // no sizeof(Split) + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue, _impl_.value_), + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue, _impl_.icon_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat_ThermostatOption, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + ~0u, // no _split_ + ~0u, // no sizeof(Split) + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat_ThermostatOption, _impl_.name_), + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat_ThermostatOption, _impl_.current_value_), + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat_ThermostatOption, _impl_.current_icon_), + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat_ThermostatOption, _impl_.options_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + ~0u, // no _split_ + ~0u, // no sizeof(Split) + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat, _impl_.thermostat_id_), + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat, _impl_.name_), + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat, _impl_.current_temperature_), + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat, _impl_.has_current_temperature_), + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat, _impl_.set_temperature_), + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat, _impl_.step_size_), + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState_Thermostat, _impl_.options_), + ~0u, // no _has_bits_ PROTOBUF_FIELD_OFFSET(::NSPanelEntityState, _internal_metadata_), ~0u, // no _extensions_ PROTOBUF_FIELD_OFFSET(::NSPanelEntityState, _impl_._oneof_case_[0]), @@ -104,42 +220,61 @@ const ::uint32_t ~0u, // no _split_ ~0u, // no sizeof(Split) ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, PROTOBUF_FIELD_OFFSET(::NSPanelEntityState, _impl_.entity_), }; static const ::_pbi::MigrationSchema schemas[] ABSL_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = { {0, -1, -1, sizeof(::NSPanelEntityState_Light)}, - {17, -1, -1, sizeof(::NSPanelEntityState)}, + {17, -1, -1, sizeof(::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue)}, + {27, -1, -1, sizeof(::NSPanelEntityState_Thermostat_ThermostatOption)}, + {39, -1, -1, sizeof(::NSPanelEntityState_Thermostat)}, + {54, -1, -1, sizeof(::NSPanelEntityState)}, }; static const ::_pb::Message* const file_default_instances[] = { &::_NSPanelEntityState_Light_default_instance_._instance, + &::_NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue_default_instance_._instance, + &::_NSPanelEntityState_Thermostat_ThermostatOption_default_instance_._instance, + &::_NSPanelEntityState_Thermostat_default_instance_._instance, &::_NSPanelEntityState_default_instance_._instance, }; const char descriptor_table_protodef_protobuf_5fnspanel_5fentity_2eproto[] ABSL_ATTRIBUTE_SECTION_VARIABLE( protodesc_cold) = { - "\n\035protobuf_nspanel_entity.proto\"\317\002\n\022NSPa" + "\n\035protobuf_nspanel_entity.proto\"\303\006\n\022NSPa" "nelEntityState\022*\n\005light\030\001 \001(\0132\031.NSPanelE" - "ntityState.LightH\000\032\202\002\n\005Light\022\020\n\010light_id" - "\030\001 \001(\005\022\014\n\004name\030\002 \001(\t\022\022\n\nbrightness\030\003 \001(\005" - "\022\022\n\ncolor_temp\030\004 \001(\005\022\013\n\003hue\030\005 \001(\005\022\022\n\nsat" - "uration\030\006 \001(\005\022\026\n\016can_color_temp\030\007 \001(\010\022\021\n" - "\tcan_color\030\010 \001(\010\022\?\n\022current_light_mode\030\t" - " \001(\0162#.NSPanelEntityState.Light.LightMod" - "e\"$\n\tLightMode\022\016\n\nCOLOR_TEMP\020\000\022\007\n\003RGB\020\001B" - "\010\n\006entityb\006proto3" + "ntityState.LightH\000\0224\n\nthermostat\030\002 \001(\0132\036" + ".NSPanelEntityState.ThermostatH\000\032\202\002\n\005Lig" + "ht\022\020\n\010light_id\030\001 \001(\005\022\014\n\004name\030\002 \001(\t\022\022\n\nbr" + "ightness\030\003 \001(\005\022\022\n\ncolor_temp\030\004 \001(\005\022\013\n\003hu" + "e\030\005 \001(\005\022\022\n\nsaturation\030\006 \001(\005\022\026\n\016can_color" + "_temp\030\007 \001(\010\022\021\n\tcan_color\030\010 \001(\010\022\?\n\022curren" + "t_light_mode\030\t \001(\0162#.NSPanelEntityState." + "Light.LightMode\"$\n\tLightMode\022\016\n\nCOLOR_TE" + "MP\020\000\022\007\n\003RGB\020\001\032\273\003\n\nThermostat\022\025\n\rthermost" + "at_id\030\001 \001(\005\022\014\n\004name\030\002 \001(\t\022\033\n\023current_tem" + "perature\030\003 \001(\002\022\037\n\027has_current_temperatur" + "e\030\004 \001(\010\022\027\n\017set_temperature\030\005 \001(\002\022\021\n\tstep" + "_size\030\006 \001(\002\022@\n\007options\030\007 \003(\0132/.NSPanelEn" + "tityState.Thermostat.ThermostatOption\032\333\001" + "\n\020ThermostatOption\022\014\n\004name\030\001 \001(\t\022\025\n\rcurr" + "ent_value\030\002 \001(\t\022\024\n\014current_icon\030\003 \001(\t\022V\n" + "\007options\030\004 \003(\0132E.NSPanelEntityState.Ther" + "mostat.ThermostatOption.ThermostatOption" + "Value\0324\n\025ThermostatOptionValue\022\r\n\005value\030" + "\001 \001(\t\022\014\n\004icon\030\002 \001(\tB\010\n\006entityb\006proto3" }; static ::absl::once_flag descriptor_table_protobuf_5fnspanel_5fentity_2eproto_once; PROTOBUF_CONSTINIT const ::_pbi::DescriptorTable descriptor_table_protobuf_5fnspanel_5fentity_2eproto = { false, false, - 377, + 877, descriptor_table_protodef_protobuf_5fnspanel_5fentity_2eproto, "protobuf_nspanel_entity.proto", &descriptor_table_protobuf_5fnspanel_5fentity_2eproto_once, nullptr, 0, - 2, + 5, schemas, file_default_instances, TableStruct_protobuf_5fnspanel_5fentity_2eproto::offsets, @@ -564,112 +699,69 @@ ::google::protobuf::Metadata NSPanelEntityState_Light::GetMetadata() const { } // =================================================================== -class NSPanelEntityState::_Internal { +class NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::_Internal { public: - static constexpr ::int32_t kOneofCaseOffset = - PROTOBUF_FIELD_OFFSET(::NSPanelEntityState, _impl_._oneof_case_); }; -void NSPanelEntityState::set_allocated_light(::NSPanelEntityState_Light* light) { - ::google::protobuf::Arena* message_arena = GetArena(); - clear_entity(); - if (light) { - ::google::protobuf::Arena* submessage_arena = light->GetArena(); - if (message_arena != submessage_arena) { - light = ::google::protobuf::internal::GetOwnedMessage(message_arena, light, submessage_arena); - } - set_has_light(); - _impl_.entity_.light_ = light; - } - // @@protoc_insertion_point(field_set_allocated:NSPanelEntityState.light) -} -NSPanelEntityState::NSPanelEntityState(::google::protobuf::Arena* arena) +NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue(::google::protobuf::Arena* arena) : ::google::protobuf::Message(arena) { SharedCtor(arena); - // @@protoc_insertion_point(arena_constructor:NSPanelEntityState) + // @@protoc_insertion_point(arena_constructor:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue) } -inline PROTOBUF_NDEBUG_INLINE NSPanelEntityState::Impl_::Impl_( +inline PROTOBUF_NDEBUG_INLINE NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::Impl_::Impl_( ::google::protobuf::internal::InternalVisibility visibility, ::google::protobuf::Arena* arena, - const Impl_& from, const ::NSPanelEntityState& from_msg) - : entity_{}, - _cached_size_{0}, - _oneof_case_{from._oneof_case_[0]} {} + const Impl_& from, const ::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& from_msg) + : value_(arena, from.value_), + icon_(arena, from.icon_), + _cached_size_{0} {} -NSPanelEntityState::NSPanelEntityState( +NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue( ::google::protobuf::Arena* arena, - const NSPanelEntityState& from) + const NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& from) : ::google::protobuf::Message(arena) { - NSPanelEntityState* const _this = this; + NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue* const _this = this; (void)_this; _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( from._internal_metadata_); new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); - switch (entity_case()) { - case ENTITY_NOT_SET: - break; - case kLight: - _impl_.entity_.light_ = ::google::protobuf::Message::CopyConstruct<::NSPanelEntityState_Light>(arena, *from._impl_.entity_.light_); - break; - } - // @@protoc_insertion_point(copy_constructor:NSPanelEntityState) + // @@protoc_insertion_point(copy_constructor:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue) } -inline PROTOBUF_NDEBUG_INLINE NSPanelEntityState::Impl_::Impl_( +inline PROTOBUF_NDEBUG_INLINE NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::Impl_::Impl_( ::google::protobuf::internal::InternalVisibility visibility, ::google::protobuf::Arena* arena) - : entity_{}, - _cached_size_{0}, - _oneof_case_{} {} + : value_(arena), + icon_(arena), + _cached_size_{0} {} -inline void NSPanelEntityState::SharedCtor(::_pb::Arena* arena) { +inline void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::SharedCtor(::_pb::Arena* arena) { new (&_impl_) Impl_(internal_visibility(), arena); } -NSPanelEntityState::~NSPanelEntityState() { - // @@protoc_insertion_point(destructor:NSPanelEntityState) +NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::~NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue() { + // @@protoc_insertion_point(destructor:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue) _internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); SharedDtor(); } -inline void NSPanelEntityState::SharedDtor() { +inline void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::SharedDtor() { ABSL_DCHECK(GetArena() == nullptr); - if (has_entity()) { - clear_entity(); - } + _impl_.value_.Destroy(); + _impl_.icon_.Destroy(); _impl_.~Impl_(); } -void NSPanelEntityState::clear_entity() { -// @@protoc_insertion_point(one_of_clear_start:NSPanelEntityState) - ::google::protobuf::internal::TSanWrite(&_impl_); - switch (entity_case()) { - case kLight: { - if (GetArena() == nullptr) { - delete _impl_.entity_.light_; - } else if (::google::protobuf::internal::DebugHardenClearOneofMessageOnArena()) { - ::google::protobuf::internal::MaybePoisonAfterClear(_impl_.entity_.light_); - } - break; - } - case ENTITY_NOT_SET: { - break; - } - } - _impl_._oneof_case_[0] = ENTITY_NOT_SET; -} - - const ::google::protobuf::MessageLite::ClassData* -NSPanelEntityState::GetClassData() const { +NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::GetClassData() const { PROTOBUF_CONSTINIT static const ::google::protobuf::MessageLite:: ClassDataFull _data_ = { { &_table_.header, nullptr, // OnDemandRegisterArenaDtor nullptr, // IsInitialized - PROTOBUF_FIELD_OFFSET(NSPanelEntityState, _impl_._cached_size_), + PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue, _impl_._cached_size_), false, }, - &NSPanelEntityState::MergeImpl, - &NSPanelEntityState::kDescriptorMethods, + &NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::MergeImpl, + &NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::kDescriptorMethods, &descriptor_table_protobuf_5fnspanel_5fentity_2eproto, nullptr, // tracker }; @@ -678,59 +770,82 @@ NSPanelEntityState::GetClassData() const { return _data_.base(); } PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 -const ::_pbi::TcParseTable<0, 1, 1, 0, 2> NSPanelEntityState::_table_ = { +const ::_pbi::TcParseTable<1, 2, 0, 86, 2> NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::_table_ = { { 0, // no _has_bits_ 0, // no _extensions_ - 1, 0, // max_field_number, fast_idx_mask + 2, 8, // max_field_number, fast_idx_mask offsetof(decltype(_table_), field_lookup_table), - 4294967294, // skipmap + 4294967292, // skipmap offsetof(decltype(_table_), field_entries), - 1, // num_field_entries - 1, // num_aux_entries - offsetof(decltype(_table_), aux_entries), - &_NSPanelEntityState_default_instance_._instance, + 2, // num_field_entries + 0, // num_aux_entries + offsetof(decltype(_table_), field_names), // no aux_entries + &_NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue_default_instance_._instance, nullptr, // post_loop_handler ::_pbi::TcParser::GenericFallback, // fallback #ifdef PROTOBUF_PREFETCH_PARSE_TABLE - ::_pbi::TcParser::GetTable<::NSPanelEntityState>(), // to_prefetch + ::_pbi::TcParser::GetTable<::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue>(), // to_prefetch #endif // PROTOBUF_PREFETCH_PARSE_TABLE }, {{ - {::_pbi::TcParser::MiniParse, {}}, + // string icon = 2; + {::_pbi::TcParser::FastUS1, + {18, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue, _impl_.icon_)}}, + // string value = 1; + {::_pbi::TcParser::FastUS1, + {10, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue, _impl_.value_)}}, }}, {{ 65535, 65535 }}, {{ - // .NSPanelEntityState.Light light = 1; - {PROTOBUF_FIELD_OFFSET(NSPanelEntityState, _impl_.entity_.light_), _Internal::kOneofCaseOffset + 0, 0, - (0 | ::_fl::kFcOneof | ::_fl::kMessage | ::_fl::kTvTable)}, - }}, {{ - {::_pbi::TcParser::GetTable<::NSPanelEntityState_Light>()}, - }}, {{ + // string value = 1; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue, _impl_.value_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string icon = 2; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue, _impl_.icon_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kUtf8String | ::_fl::kRepAString)}, + }}, + // no aux_entries + {{ + "\104\5\4\0\0\0\0\0" + "NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue" + "value" + "icon" }}, }; -PROTOBUF_NOINLINE void NSPanelEntityState::Clear() { -// @@protoc_insertion_point(message_clear_start:NSPanelEntityState) +PROTOBUF_NOINLINE void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::Clear() { +// @@protoc_insertion_point(message_clear_start:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue) ::google::protobuf::internal::TSanWrite(&_impl_); ::uint32_t cached_has_bits = 0; // Prevent compiler warnings about cached_has_bits being unused (void) cached_has_bits; - clear_entity(); + _impl_.value_.ClearToEmpty(); + _impl_.icon_.ClearToEmpty(); _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); } -::uint8_t* NSPanelEntityState::_InternalSerialize( +::uint8_t* NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::_InternalSerialize( ::uint8_t* target, ::google::protobuf::io::EpsCopyOutputStream* stream) const { - // @@protoc_insertion_point(serialize_to_array_start:NSPanelEntityState) + // @@protoc_insertion_point(serialize_to_array_start:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue) ::uint32_t cached_has_bits = 0; (void)cached_has_bits; - // .NSPanelEntityState.Light light = 1; - if (entity_case() == kLight) { - target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( - 1, *_impl_.entity_.light_, _impl_.entity_.light_->GetCachedSize(), target, stream); + // string value = 1; + if (!this->_internal_value().empty()) { + const std::string& _s = this->_internal_value(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue.value"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + + // string icon = 2; + if (!this->_internal_icon().empty()) { + const std::string& _s = this->_internal_icon(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue.icon"); + target = stream->WriteStringMaybeAliased(2, _s, target); } if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { @@ -738,59 +853,1026 @@ ::uint8_t* NSPanelEntityState::_InternalSerialize( ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); } - // @@protoc_insertion_point(serialize_to_array_end:NSPanelEntityState) + // @@protoc_insertion_point(serialize_to_array_end:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue) return target; } -::size_t NSPanelEntityState::ByteSizeLong() const { -// @@protoc_insertion_point(message_byte_size_start:NSPanelEntityState) +::size_t NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue) ::size_t total_size = 0; ::uint32_t cached_has_bits = 0; // Prevent compiler warnings about cached_has_bits being unused (void) cached_has_bits; - switch (entity_case()) { - // .NSPanelEntityState.Light light = 1; - case kLight: { - total_size += - 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*_impl_.entity_.light_); - break; - } - case ENTITY_NOT_SET: { - break; - } + ::_pbi::Prefetch5LinesFrom7Lines(reinterpret_cast(this)); + // string value = 1; + if (!this->_internal_value().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this->_internal_value()); + } + + // string icon = 2; + if (!this->_internal_icon().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this->_internal_icon()); } + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); } -void NSPanelEntityState::MergeImpl(::google::protobuf::MessageLite& to_msg, const ::google::protobuf::MessageLite& from_msg) { - auto* const _this = static_cast(&to_msg); - auto& from = static_cast(from_msg); - ::google::protobuf::Arena* arena = _this->GetArena(); - // @@protoc_insertion_point(class_specific_merge_from_start:NSPanelEntityState) +void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::MergeImpl(::google::protobuf::MessageLite& to_msg, const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue) ABSL_DCHECK_NE(&from, _this); ::uint32_t cached_has_bits = 0; (void) cached_has_bits; - if (const uint32_t oneof_from_case = from._impl_._oneof_case_[0]) { - const uint32_t oneof_to_case = _this->_impl_._oneof_case_[0]; - const bool oneof_needs_init = oneof_to_case != oneof_from_case; - if (oneof_needs_init) { - if (oneof_to_case != 0) { - _this->clear_entity(); - } - _this->_impl_._oneof_case_[0] = oneof_from_case; - } + if (!from._internal_value().empty()) { + _this->_internal_set_value(from._internal_value()); + } + if (!from._internal_icon().empty()) { + _this->_internal_set_icon(from._internal_icon()); + } + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>(from._internal_metadata_); +} - switch (oneof_from_case) { - case kLight: { - if (oneof_needs_init) { - _this->_impl_.entity_.light_ = - ::google::protobuf::Message::CopyConstruct<::NSPanelEntityState_Light>(arena, *from._impl_.entity_.light_); - } else { - _this->_impl_.entity_.light_->MergeFrom(from._internal_light()); +void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::CopyFrom(const NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::InternalSwap(NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue* PROTOBUF_RESTRICT other) { + using std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.value_, &other->_impl_.value_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.icon_, &other->_impl_.icon_, arena); +} + +::google::protobuf::Metadata NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::GetMetadata() const { + return ::google::protobuf::Message::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +class NSPanelEntityState_Thermostat_ThermostatOption::_Internal { + public: +}; + +NSPanelEntityState_Thermostat_ThermostatOption::NSPanelEntityState_Thermostat_ThermostatOption(::google::protobuf::Arena* arena) + : ::google::protobuf::Message(arena) { + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:NSPanelEntityState.Thermostat.ThermostatOption) +} +inline PROTOBUF_NDEBUG_INLINE NSPanelEntityState_Thermostat_ThermostatOption::Impl_::Impl_( + ::google::protobuf::internal::InternalVisibility visibility, ::google::protobuf::Arena* arena, + const Impl_& from, const ::NSPanelEntityState_Thermostat_ThermostatOption& from_msg) + : options_{visibility, arena, from.options_}, + name_(arena, from.name_), + current_value_(arena, from.current_value_), + current_icon_(arena, from.current_icon_), + _cached_size_{0} {} + +NSPanelEntityState_Thermostat_ThermostatOption::NSPanelEntityState_Thermostat_ThermostatOption( + ::google::protobuf::Arena* arena, + const NSPanelEntityState_Thermostat_ThermostatOption& from) + : ::google::protobuf::Message(arena) { + NSPanelEntityState_Thermostat_ThermostatOption* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + + // @@protoc_insertion_point(copy_constructor:NSPanelEntityState.Thermostat.ThermostatOption) +} +inline PROTOBUF_NDEBUG_INLINE NSPanelEntityState_Thermostat_ThermostatOption::Impl_::Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena) + : options_{visibility, arena}, + name_(arena), + current_value_(arena), + current_icon_(arena), + _cached_size_{0} {} + +inline void NSPanelEntityState_Thermostat_ThermostatOption::SharedCtor(::_pb::Arena* arena) { + new (&_impl_) Impl_(internal_visibility(), arena); +} +NSPanelEntityState_Thermostat_ThermostatOption::~NSPanelEntityState_Thermostat_ThermostatOption() { + // @@protoc_insertion_point(destructor:NSPanelEntityState.Thermostat.ThermostatOption) + _internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + SharedDtor(); +} +inline void NSPanelEntityState_Thermostat_ThermostatOption::SharedDtor() { + ABSL_DCHECK(GetArena() == nullptr); + _impl_.name_.Destroy(); + _impl_.current_value_.Destroy(); + _impl_.current_icon_.Destroy(); + _impl_.~Impl_(); +} + +const ::google::protobuf::MessageLite::ClassData* +NSPanelEntityState_Thermostat_ThermostatOption::GetClassData() const { + PROTOBUF_CONSTINIT static const ::google::protobuf::MessageLite:: + ClassDataFull _data_ = { + { + &_table_.header, + nullptr, // OnDemandRegisterArenaDtor + nullptr, // IsInitialized + PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption, _impl_._cached_size_), + false, + }, + &NSPanelEntityState_Thermostat_ThermostatOption::MergeImpl, + &NSPanelEntityState_Thermostat_ThermostatOption::kDescriptorMethods, + &descriptor_table_protobuf_5fnspanel_5fentity_2eproto, + nullptr, // tracker + }; + ::google::protobuf::internal::PrefetchToLocalCache(&_data_); + ::google::protobuf::internal::PrefetchToLocalCache(_data_.tc_table); + return _data_.base(); +} +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 +const ::_pbi::TcParseTable<2, 4, 1, 84, 2> NSPanelEntityState_Thermostat_ThermostatOption::_table_ = { + { + 0, // no _has_bits_ + 0, // no _extensions_ + 4, 24, // max_field_number, fast_idx_mask + offsetof(decltype(_table_), field_lookup_table), + 4294967280, // skipmap + offsetof(decltype(_table_), field_entries), + 4, // num_field_entries + 1, // num_aux_entries + offsetof(decltype(_table_), aux_entries), + &_NSPanelEntityState_Thermostat_ThermostatOption_default_instance_._instance, + nullptr, // post_loop_handler + ::_pbi::TcParser::GenericFallback, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::NSPanelEntityState_Thermostat_ThermostatOption>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // repeated .NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue options = 4; + {::_pbi::TcParser::FastMtR1, + {34, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption, _impl_.options_)}}, + // string name = 1; + {::_pbi::TcParser::FastUS1, + {10, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption, _impl_.name_)}}, + // string current_value = 2; + {::_pbi::TcParser::FastUS1, + {18, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption, _impl_.current_value_)}}, + // string current_icon = 3; + {::_pbi::TcParser::FastUS1, + {26, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption, _impl_.current_icon_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // string name = 1; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption, _impl_.name_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string current_value = 2; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption, _impl_.current_value_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string current_icon = 3; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption, _impl_.current_icon_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // repeated .NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue options = 4; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat_ThermostatOption, _impl_.options_), 0, 0, + (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, {{ + {::_pbi::TcParser::GetTable<::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue>()}, + }}, {{ + "\56\4\15\14\0\0\0\0" + "NSPanelEntityState.Thermostat.ThermostatOption" + "name" + "current_value" + "current_icon" + }}, +}; + +PROTOBUF_NOINLINE void NSPanelEntityState_Thermostat_ThermostatOption::Clear() { +// @@protoc_insertion_point(message_clear_start:NSPanelEntityState.Thermostat.ThermostatOption) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.options_.Clear(); + _impl_.name_.ClearToEmpty(); + _impl_.current_value_.ClearToEmpty(); + _impl_.current_icon_.ClearToEmpty(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +::uint8_t* NSPanelEntityState_Thermostat_ThermostatOption::_InternalSerialize( + ::uint8_t* target, + ::google::protobuf::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:NSPanelEntityState.Thermostat.ThermostatOption) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + // string name = 1; + if (!this->_internal_name().empty()) { + const std::string& _s = this->_internal_name(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "NSPanelEntityState.Thermostat.ThermostatOption.name"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + + // string current_value = 2; + if (!this->_internal_current_value().empty()) { + const std::string& _s = this->_internal_current_value(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "NSPanelEntityState.Thermostat.ThermostatOption.current_value"); + target = stream->WriteStringMaybeAliased(2, _s, target); + } + + // string current_icon = 3; + if (!this->_internal_current_icon().empty()) { + const std::string& _s = this->_internal_current_icon(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "NSPanelEntityState.Thermostat.ThermostatOption.current_icon"); + target = stream->WriteStringMaybeAliased(3, _s, target); + } + + // repeated .NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue options = 4; + for (unsigned i = 0, n = static_cast( + this->_internal_options_size()); + i < n; i++) { + const auto& repfield = this->_internal_options().Get(i); + target = + ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, repfield, repfield.GetCachedSize(), + target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:NSPanelEntityState.Thermostat.ThermostatOption) + return target; +} + +::size_t NSPanelEntityState_Thermostat_ThermostatOption::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:NSPanelEntityState.Thermostat.ThermostatOption) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + ::_pbi::Prefetch5LinesFrom7Lines(reinterpret_cast(this)); + // repeated .NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue options = 4; + total_size += 1UL * this->_internal_options_size(); + for (const auto& msg : this->_internal_options()) { + total_size += ::google::protobuf::internal::WireFormatLite::MessageSize(msg); + } + // string name = 1; + if (!this->_internal_name().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this->_internal_name()); + } + + // string current_value = 2; + if (!this->_internal_current_value().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this->_internal_current_value()); + } + + // string current_icon = 3; + if (!this->_internal_current_icon().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this->_internal_current_icon()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + + +void NSPanelEntityState_Thermostat_ThermostatOption::MergeImpl(::google::protobuf::MessageLite& to_msg, const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:NSPanelEntityState.Thermostat.ThermostatOption) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_internal_mutable_options()->MergeFrom( + from._internal_options()); + if (!from._internal_name().empty()) { + _this->_internal_set_name(from._internal_name()); + } + if (!from._internal_current_value().empty()) { + _this->_internal_set_current_value(from._internal_current_value()); + } + if (!from._internal_current_icon().empty()) { + _this->_internal_set_current_icon(from._internal_current_icon()); + } + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>(from._internal_metadata_); +} + +void NSPanelEntityState_Thermostat_ThermostatOption::CopyFrom(const NSPanelEntityState_Thermostat_ThermostatOption& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:NSPanelEntityState.Thermostat.ThermostatOption) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void NSPanelEntityState_Thermostat_ThermostatOption::InternalSwap(NSPanelEntityState_Thermostat_ThermostatOption* PROTOBUF_RESTRICT other) { + using std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.options_.InternalSwap(&other->_impl_.options_); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.name_, &other->_impl_.name_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.current_value_, &other->_impl_.current_value_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.current_icon_, &other->_impl_.current_icon_, arena); +} + +::google::protobuf::Metadata NSPanelEntityState_Thermostat_ThermostatOption::GetMetadata() const { + return ::google::protobuf::Message::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +class NSPanelEntityState_Thermostat::_Internal { + public: +}; + +NSPanelEntityState_Thermostat::NSPanelEntityState_Thermostat(::google::protobuf::Arena* arena) + : ::google::protobuf::Message(arena) { + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:NSPanelEntityState.Thermostat) +} +inline PROTOBUF_NDEBUG_INLINE NSPanelEntityState_Thermostat::Impl_::Impl_( + ::google::protobuf::internal::InternalVisibility visibility, ::google::protobuf::Arena* arena, + const Impl_& from, const ::NSPanelEntityState_Thermostat& from_msg) + : options_{visibility, arena, from.options_}, + name_(arena, from.name_), + _cached_size_{0} {} + +NSPanelEntityState_Thermostat::NSPanelEntityState_Thermostat( + ::google::protobuf::Arena* arena, + const NSPanelEntityState_Thermostat& from) + : ::google::protobuf::Message(arena) { + NSPanelEntityState_Thermostat* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, thermostat_id_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, thermostat_id_), + offsetof(Impl_, step_size_) - + offsetof(Impl_, thermostat_id_) + + sizeof(Impl_::step_size_)); + + // @@protoc_insertion_point(copy_constructor:NSPanelEntityState.Thermostat) +} +inline PROTOBUF_NDEBUG_INLINE NSPanelEntityState_Thermostat::Impl_::Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena) + : options_{visibility, arena}, + name_(arena), + _cached_size_{0} {} + +inline void NSPanelEntityState_Thermostat::SharedCtor(::_pb::Arena* arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, thermostat_id_), + 0, + offsetof(Impl_, step_size_) - + offsetof(Impl_, thermostat_id_) + + sizeof(Impl_::step_size_)); +} +NSPanelEntityState_Thermostat::~NSPanelEntityState_Thermostat() { + // @@protoc_insertion_point(destructor:NSPanelEntityState.Thermostat) + _internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + SharedDtor(); +} +inline void NSPanelEntityState_Thermostat::SharedDtor() { + ABSL_DCHECK(GetArena() == nullptr); + _impl_.name_.Destroy(); + _impl_.~Impl_(); +} + +const ::google::protobuf::MessageLite::ClassData* +NSPanelEntityState_Thermostat::GetClassData() const { + PROTOBUF_CONSTINIT static const ::google::protobuf::MessageLite:: + ClassDataFull _data_ = { + { + &_table_.header, + nullptr, // OnDemandRegisterArenaDtor + nullptr, // IsInitialized + PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_._cached_size_), + false, + }, + &NSPanelEntityState_Thermostat::MergeImpl, + &NSPanelEntityState_Thermostat::kDescriptorMethods, + &descriptor_table_protobuf_5fnspanel_5fentity_2eproto, + nullptr, // tracker + }; + ::google::protobuf::internal::PrefetchToLocalCache(&_data_); + ::google::protobuf::internal::PrefetchToLocalCache(_data_.tc_table); + return _data_.base(); +} +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 +const ::_pbi::TcParseTable<3, 7, 1, 42, 2> NSPanelEntityState_Thermostat::_table_ = { + { + 0, // no _has_bits_ + 0, // no _extensions_ + 7, 56, // max_field_number, fast_idx_mask + offsetof(decltype(_table_), field_lookup_table), + 4294967168, // skipmap + offsetof(decltype(_table_), field_entries), + 7, // num_field_entries + 1, // num_aux_entries + offsetof(decltype(_table_), aux_entries), + &_NSPanelEntityState_Thermostat_default_instance_._instance, + nullptr, // post_loop_handler + ::_pbi::TcParser::GenericFallback, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::NSPanelEntityState_Thermostat>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // int32 thermostat_id = 1; + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(NSPanelEntityState_Thermostat, _impl_.thermostat_id_), 63>(), + {8, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.thermostat_id_)}}, + // string name = 2; + {::_pbi::TcParser::FastUS1, + {18, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.name_)}}, + // float current_temperature = 3; + {::_pbi::TcParser::FastF32S1, + {29, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.current_temperature_)}}, + // bool has_current_temperature = 4; + {::_pbi::TcParser::SingularVarintNoZag1(), + {32, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.has_current_temperature_)}}, + // float set_temperature = 5; + {::_pbi::TcParser::FastF32S1, + {45, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.set_temperature_)}}, + // float step_size = 6; + {::_pbi::TcParser::FastF32S1, + {53, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.step_size_)}}, + // repeated .NSPanelEntityState.Thermostat.ThermostatOption options = 7; + {::_pbi::TcParser::FastMtR1, + {58, 63, 0, PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.options_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // int32 thermostat_id = 1; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.thermostat_id_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kInt32)}, + // string name = 2; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.name_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // float current_temperature = 3; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.current_temperature_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kFloat)}, + // bool has_current_temperature = 4; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.has_current_temperature_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kBool)}, + // float set_temperature = 5; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.set_temperature_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kFloat)}, + // float step_size = 6; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.step_size_), 0, 0, + (0 | ::_fl::kFcSingular | ::_fl::kFloat)}, + // repeated .NSPanelEntityState.Thermostat.ThermostatOption options = 7; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.options_), 0, 0, + (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, {{ + {::_pbi::TcParser::GetTable<::NSPanelEntityState_Thermostat_ThermostatOption>()}, + }}, {{ + "\35\0\4\0\0\0\0\0" + "NSPanelEntityState.Thermostat" + "name" + }}, +}; + +PROTOBUF_NOINLINE void NSPanelEntityState_Thermostat::Clear() { +// @@protoc_insertion_point(message_clear_start:NSPanelEntityState.Thermostat) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.options_.Clear(); + _impl_.name_.ClearToEmpty(); + ::memset(&_impl_.thermostat_id_, 0, static_cast<::size_t>( + reinterpret_cast(&_impl_.step_size_) - + reinterpret_cast(&_impl_.thermostat_id_)) + sizeof(_impl_.step_size_)); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +::uint8_t* NSPanelEntityState_Thermostat::_InternalSerialize( + ::uint8_t* target, + ::google::protobuf::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:NSPanelEntityState.Thermostat) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + // int32 thermostat_id = 1; + if (this->_internal_thermostat_id() != 0) { + target = ::google::protobuf::internal::WireFormatLite:: + WriteInt32ToArrayWithField<1>( + stream, this->_internal_thermostat_id(), target); + } + + // string name = 2; + if (!this->_internal_name().empty()) { + const std::string& _s = this->_internal_name(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "NSPanelEntityState.Thermostat.name"); + target = stream->WriteStringMaybeAliased(2, _s, target); + } + + // float current_temperature = 3; + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_current_temperature = this->_internal_current_temperature(); + ::uint32_t raw_current_temperature; + memcpy(&raw_current_temperature, &tmp_current_temperature, sizeof(tmp_current_temperature)); + if (raw_current_temperature != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray( + 3, this->_internal_current_temperature(), target); + } + + // bool has_current_temperature = 4; + if (this->_internal_has_current_temperature() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 4, this->_internal_has_current_temperature(), target); + } + + // float set_temperature = 5; + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_set_temperature = this->_internal_set_temperature(); + ::uint32_t raw_set_temperature; + memcpy(&raw_set_temperature, &tmp_set_temperature, sizeof(tmp_set_temperature)); + if (raw_set_temperature != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray( + 5, this->_internal_set_temperature(), target); + } + + // float step_size = 6; + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_step_size = this->_internal_step_size(); + ::uint32_t raw_step_size; + memcpy(&raw_step_size, &tmp_step_size, sizeof(tmp_step_size)); + if (raw_step_size != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFloatToArray( + 6, this->_internal_step_size(), target); + } + + // repeated .NSPanelEntityState.Thermostat.ThermostatOption options = 7; + for (unsigned i = 0, n = static_cast( + this->_internal_options_size()); + i < n; i++) { + const auto& repfield = this->_internal_options().Get(i); + target = + ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 7, repfield, repfield.GetCachedSize(), + target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:NSPanelEntityState.Thermostat) + return target; +} + +::size_t NSPanelEntityState_Thermostat::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:NSPanelEntityState.Thermostat) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + ::_pbi::Prefetch5LinesFrom7Lines(reinterpret_cast(this)); + // repeated .NSPanelEntityState.Thermostat.ThermostatOption options = 7; + total_size += 1UL * this->_internal_options_size(); + for (const auto& msg : this->_internal_options()) { + total_size += ::google::protobuf::internal::WireFormatLite::MessageSize(msg); + } + // string name = 2; + if (!this->_internal_name().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this->_internal_name()); + } + + // int32 thermostat_id = 1; + if (this->_internal_thermostat_id() != 0) { + total_size += ::_pbi::WireFormatLite::Int32SizePlusOne( + this->_internal_thermostat_id()); + } + + // float current_temperature = 3; + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_current_temperature = this->_internal_current_temperature(); + ::uint32_t raw_current_temperature; + memcpy(&raw_current_temperature, &tmp_current_temperature, sizeof(tmp_current_temperature)); + if (raw_current_temperature != 0) { + total_size += 5; + } + + // bool has_current_temperature = 4; + if (this->_internal_has_current_temperature() != 0) { + total_size += 2; + } + + // float set_temperature = 5; + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_set_temperature = this->_internal_set_temperature(); + ::uint32_t raw_set_temperature; + memcpy(&raw_set_temperature, &tmp_set_temperature, sizeof(tmp_set_temperature)); + if (raw_set_temperature != 0) { + total_size += 5; + } + + // float step_size = 6; + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_step_size = this->_internal_step_size(); + ::uint32_t raw_step_size; + memcpy(&raw_step_size, &tmp_step_size, sizeof(tmp_step_size)); + if (raw_step_size != 0) { + total_size += 5; + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + + +void NSPanelEntityState_Thermostat::MergeImpl(::google::protobuf::MessageLite& to_msg, const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:NSPanelEntityState.Thermostat) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_internal_mutable_options()->MergeFrom( + from._internal_options()); + if (!from._internal_name().empty()) { + _this->_internal_set_name(from._internal_name()); + } + if (from._internal_thermostat_id() != 0) { + _this->_impl_.thermostat_id_ = from._impl_.thermostat_id_; + } + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_current_temperature = from._internal_current_temperature(); + ::uint32_t raw_current_temperature; + memcpy(&raw_current_temperature, &tmp_current_temperature, sizeof(tmp_current_temperature)); + if (raw_current_temperature != 0) { + _this->_impl_.current_temperature_ = from._impl_.current_temperature_; + } + if (from._internal_has_current_temperature() != 0) { + _this->_impl_.has_current_temperature_ = from._impl_.has_current_temperature_; + } + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_set_temperature = from._internal_set_temperature(); + ::uint32_t raw_set_temperature; + memcpy(&raw_set_temperature, &tmp_set_temperature, sizeof(tmp_set_temperature)); + if (raw_set_temperature != 0) { + _this->_impl_.set_temperature_ = from._impl_.set_temperature_; + } + static_assert(sizeof(::uint32_t) == sizeof(float), + "Code assumes ::uint32_t and float are the same size."); + float tmp_step_size = from._internal_step_size(); + ::uint32_t raw_step_size; + memcpy(&raw_step_size, &tmp_step_size, sizeof(tmp_step_size)); + if (raw_step_size != 0) { + _this->_impl_.step_size_ = from._impl_.step_size_; + } + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>(from._internal_metadata_); +} + +void NSPanelEntityState_Thermostat::CopyFrom(const NSPanelEntityState_Thermostat& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:NSPanelEntityState.Thermostat) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void NSPanelEntityState_Thermostat::InternalSwap(NSPanelEntityState_Thermostat* PROTOBUF_RESTRICT other) { + using std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + _impl_.options_.InternalSwap(&other->_impl_.options_); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.name_, &other->_impl_.name_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.step_size_) + + sizeof(NSPanelEntityState_Thermostat::_impl_.step_size_) + - PROTOBUF_FIELD_OFFSET(NSPanelEntityState_Thermostat, _impl_.thermostat_id_)>( + reinterpret_cast(&_impl_.thermostat_id_), + reinterpret_cast(&other->_impl_.thermostat_id_)); +} + +::google::protobuf::Metadata NSPanelEntityState_Thermostat::GetMetadata() const { + return ::google::protobuf::Message::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +class NSPanelEntityState::_Internal { + public: + static constexpr ::int32_t kOneofCaseOffset = + PROTOBUF_FIELD_OFFSET(::NSPanelEntityState, _impl_._oneof_case_); +}; + +void NSPanelEntityState::set_allocated_light(::NSPanelEntityState_Light* light) { + ::google::protobuf::Arena* message_arena = GetArena(); + clear_entity(); + if (light) { + ::google::protobuf::Arena* submessage_arena = light->GetArena(); + if (message_arena != submessage_arena) { + light = ::google::protobuf::internal::GetOwnedMessage(message_arena, light, submessage_arena); + } + set_has_light(); + _impl_.entity_.light_ = light; + } + // @@protoc_insertion_point(field_set_allocated:NSPanelEntityState.light) +} +void NSPanelEntityState::set_allocated_thermostat(::NSPanelEntityState_Thermostat* thermostat) { + ::google::protobuf::Arena* message_arena = GetArena(); + clear_entity(); + if (thermostat) { + ::google::protobuf::Arena* submessage_arena = thermostat->GetArena(); + if (message_arena != submessage_arena) { + thermostat = ::google::protobuf::internal::GetOwnedMessage(message_arena, thermostat, submessage_arena); + } + set_has_thermostat(); + _impl_.entity_.thermostat_ = thermostat; + } + // @@protoc_insertion_point(field_set_allocated:NSPanelEntityState.thermostat) +} +NSPanelEntityState::NSPanelEntityState(::google::protobuf::Arena* arena) + : ::google::protobuf::Message(arena) { + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:NSPanelEntityState) +} +inline PROTOBUF_NDEBUG_INLINE NSPanelEntityState::Impl_::Impl_( + ::google::protobuf::internal::InternalVisibility visibility, ::google::protobuf::Arena* arena, + const Impl_& from, const ::NSPanelEntityState& from_msg) + : entity_{}, + _cached_size_{0}, + _oneof_case_{from._oneof_case_[0]} {} + +NSPanelEntityState::NSPanelEntityState( + ::google::protobuf::Arena* arena, + const NSPanelEntityState& from) + : ::google::protobuf::Message(arena) { + NSPanelEntityState* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + switch (entity_case()) { + case ENTITY_NOT_SET: + break; + case kLight: + _impl_.entity_.light_ = ::google::protobuf::Message::CopyConstruct<::NSPanelEntityState_Light>(arena, *from._impl_.entity_.light_); + break; + case kThermostat: + _impl_.entity_.thermostat_ = ::google::protobuf::Message::CopyConstruct<::NSPanelEntityState_Thermostat>(arena, *from._impl_.entity_.thermostat_); + break; + } + + // @@protoc_insertion_point(copy_constructor:NSPanelEntityState) +} +inline PROTOBUF_NDEBUG_INLINE NSPanelEntityState::Impl_::Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena) + : entity_{}, + _cached_size_{0}, + _oneof_case_{} {} + +inline void NSPanelEntityState::SharedCtor(::_pb::Arena* arena) { + new (&_impl_) Impl_(internal_visibility(), arena); +} +NSPanelEntityState::~NSPanelEntityState() { + // @@protoc_insertion_point(destructor:NSPanelEntityState) + _internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + SharedDtor(); +} +inline void NSPanelEntityState::SharedDtor() { + ABSL_DCHECK(GetArena() == nullptr); + if (has_entity()) { + clear_entity(); + } + _impl_.~Impl_(); +} + +void NSPanelEntityState::clear_entity() { +// @@protoc_insertion_point(one_of_clear_start:NSPanelEntityState) + ::google::protobuf::internal::TSanWrite(&_impl_); + switch (entity_case()) { + case kLight: { + if (GetArena() == nullptr) { + delete _impl_.entity_.light_; + } else if (::google::protobuf::internal::DebugHardenClearOneofMessageOnArena()) { + ::google::protobuf::internal::MaybePoisonAfterClear(_impl_.entity_.light_); + } + break; + } + case kThermostat: { + if (GetArena() == nullptr) { + delete _impl_.entity_.thermostat_; + } else if (::google::protobuf::internal::DebugHardenClearOneofMessageOnArena()) { + ::google::protobuf::internal::MaybePoisonAfterClear(_impl_.entity_.thermostat_); + } + break; + } + case ENTITY_NOT_SET: { + break; + } + } + _impl_._oneof_case_[0] = ENTITY_NOT_SET; +} + + +const ::google::protobuf::MessageLite::ClassData* +NSPanelEntityState::GetClassData() const { + PROTOBUF_CONSTINIT static const ::google::protobuf::MessageLite:: + ClassDataFull _data_ = { + { + &_table_.header, + nullptr, // OnDemandRegisterArenaDtor + nullptr, // IsInitialized + PROTOBUF_FIELD_OFFSET(NSPanelEntityState, _impl_._cached_size_), + false, + }, + &NSPanelEntityState::MergeImpl, + &NSPanelEntityState::kDescriptorMethods, + &descriptor_table_protobuf_5fnspanel_5fentity_2eproto, + nullptr, // tracker + }; + ::google::protobuf::internal::PrefetchToLocalCache(&_data_); + ::google::protobuf::internal::PrefetchToLocalCache(_data_.tc_table); + return _data_.base(); +} +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 +const ::_pbi::TcParseTable<0, 2, 2, 0, 2> NSPanelEntityState::_table_ = { + { + 0, // no _has_bits_ + 0, // no _extensions_ + 2, 0, // max_field_number, fast_idx_mask + offsetof(decltype(_table_), field_lookup_table), + 4294967292, // skipmap + offsetof(decltype(_table_), field_entries), + 2, // num_field_entries + 2, // num_aux_entries + offsetof(decltype(_table_), aux_entries), + &_NSPanelEntityState_default_instance_._instance, + nullptr, // post_loop_handler + ::_pbi::TcParser::GenericFallback, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::NSPanelEntityState>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // .NSPanelEntityState.Light light = 1; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState, _impl_.entity_.light_), _Internal::kOneofCaseOffset + 0, 0, + (0 | ::_fl::kFcOneof | ::_fl::kMessage | ::_fl::kTvTable)}, + // .NSPanelEntityState.Thermostat thermostat = 2; + {PROTOBUF_FIELD_OFFSET(NSPanelEntityState, _impl_.entity_.thermostat_), _Internal::kOneofCaseOffset + 0, 1, + (0 | ::_fl::kFcOneof | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, {{ + {::_pbi::TcParser::GetTable<::NSPanelEntityState_Light>()}, + {::_pbi::TcParser::GetTable<::NSPanelEntityState_Thermostat>()}, + }}, {{ + }}, +}; + +PROTOBUF_NOINLINE void NSPanelEntityState::Clear() { +// @@protoc_insertion_point(message_clear_start:NSPanelEntityState) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + clear_entity(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +::uint8_t* NSPanelEntityState::_InternalSerialize( + ::uint8_t* target, + ::google::protobuf::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:NSPanelEntityState) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + switch (entity_case()) { + case kLight: { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 1, *_impl_.entity_.light_, _impl_.entity_.light_->GetCachedSize(), target, stream); + break; + } + case kThermostat: { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 2, *_impl_.entity_.thermostat_, _impl_.entity_.thermostat_->GetCachedSize(), target, stream); + break; + } + default: + break; + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:NSPanelEntityState) + return target; +} + +::size_t NSPanelEntityState::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:NSPanelEntityState) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + switch (entity_case()) { + // .NSPanelEntityState.Light light = 1; + case kLight: { + total_size += + 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*_impl_.entity_.light_); + break; + } + // .NSPanelEntityState.Thermostat thermostat = 2; + case kThermostat: { + total_size += + 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*_impl_.entity_.thermostat_); + break; + } + case ENTITY_NOT_SET: { + break; + } + } + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + + +void NSPanelEntityState::MergeImpl(::google::protobuf::MessageLite& to_msg, const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:NSPanelEntityState) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (const uint32_t oneof_from_case = from._impl_._oneof_case_[0]) { + const uint32_t oneof_to_case = _this->_impl_._oneof_case_[0]; + const bool oneof_needs_init = oneof_to_case != oneof_from_case; + if (oneof_needs_init) { + if (oneof_to_case != 0) { + _this->clear_entity(); + } + _this->_impl_._oneof_case_[0] = oneof_from_case; + } + + switch (oneof_from_case) { + case kLight: { + if (oneof_needs_init) { + _this->_impl_.entity_.light_ = + ::google::protobuf::Message::CopyConstruct<::NSPanelEntityState_Light>(arena, *from._impl_.entity_.light_); + } else { + _this->_impl_.entity_.light_->MergeFrom(from._internal_light()); + } + break; + } + case kThermostat: { + if (oneof_needs_init) { + _this->_impl_.entity_.thermostat_ = + ::google::protobuf::Message::CopyConstruct<::NSPanelEntityState_Thermostat>(arena, *from._impl_.entity_.thermostat_); + } else { + _this->_impl_.entity_.thermostat_->MergeFrom(from._internal_thermostat()); } break; } diff --git a/docker/MQTTManager/include/protobuf/protobuf_nspanel_entity.pb.h b/docker/MQTTManager/include/protobuf/protobuf_nspanel_entity.pb.h index 868c2e00..3b4e286d 100644 --- a/docker/MQTTManager/include/protobuf/protobuf_nspanel_entity.pb.h +++ b/docker/MQTTManager/include/protobuf/protobuf_nspanel_entity.pb.h @@ -56,6 +56,15 @@ extern NSPanelEntityStateDefaultTypeInternal _NSPanelEntityState_default_instanc class NSPanelEntityState_Light; struct NSPanelEntityState_LightDefaultTypeInternal; extern NSPanelEntityState_LightDefaultTypeInternal _NSPanelEntityState_Light_default_instance_; +class NSPanelEntityState_Thermostat; +struct NSPanelEntityState_ThermostatDefaultTypeInternal; +extern NSPanelEntityState_ThermostatDefaultTypeInternal _NSPanelEntityState_Thermostat_default_instance_; +class NSPanelEntityState_Thermostat_ThermostatOption; +struct NSPanelEntityState_Thermostat_ThermostatOptionDefaultTypeInternal; +extern NSPanelEntityState_Thermostat_ThermostatOptionDefaultTypeInternal _NSPanelEntityState_Thermostat_ThermostatOption_default_instance_; +class NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue; +struct NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValueDefaultTypeInternal; +extern NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValueDefaultTypeInternal _NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue_default_instance_; namespace google { namespace protobuf { } // namespace protobuf @@ -100,6 +109,201 @@ inline bool NSPanelEntityState_Light_LightMode_Parse(absl::string_view name, NSP // ------------------------------------------------------------------- +class NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue) */ { + public: + inline NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue() : NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue(nullptr) {} + ~NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue() override; + template + explicit PROTOBUF_CONSTEXPR NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue( + ::google::protobuf::internal::ConstantInitialized); + + inline NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue(const NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& from) : NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue(nullptr, from) {} + inline NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue(NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue&& from) noexcept + : NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue(nullptr, std::move(from)) {} + inline NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& operator=(const NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& from) { + CopyFrom(from); + return *this; + } + inline NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& operator=(NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue&& from) noexcept { + if (this == &from) return *this; + if (GetArena() == from.GetArena() +#ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetArena() != nullptr +#endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + static const ::google::protobuf::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::google::protobuf::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::google::protobuf::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& default_instance() { + return *internal_default_instance(); + } + static inline const NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue* internal_default_instance() { + return reinterpret_cast( + &_NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue_default_instance_); + } + static constexpr int kIndexInFileMessages = 1; + friend void swap(NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& a, NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& b) { a.Swap(&b); } + inline void Swap(NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue* other) { + if (other == this) return; +#ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetArena() != nullptr && GetArena() == other->GetArena()) { +#else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetArena() == other->GetArena()) { +#endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue* other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue* New(::google::protobuf::Arena* arena = nullptr) const final { + return ::google::protobuf::Message::DefaultConstruct(arena); + } + using ::google::protobuf::Message::CopyFrom; + void CopyFrom(const NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& from); + using ::google::protobuf::Message::MergeFrom; + void MergeFrom(const NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& from) { NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::MergeImpl(*this, from); } + + private: + static void MergeImpl( + ::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() final; + ::size_t ByteSizeLong() const final; + ::uint8_t* _InternalSerialize( + ::uint8_t* target, + ::google::protobuf::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::google::protobuf::Arena* arena); + void SharedDtor(); + void InternalSwap(NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue* other); + private: + friend class ::google::protobuf::internal::AnyMetadata; + static ::absl::string_view FullMessageName() { return "NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue"; } + + protected: + explicit NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue(::google::protobuf::Arena* arena); + NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue(::google::protobuf::Arena* arena, const NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& from); + NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue(::google::protobuf::Arena* arena, NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue&& from) noexcept + : NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::Message::ClassData* GetClassData() const final; + + public: + ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kValueFieldNumber = 1, + kIconFieldNumber = 2, + }; + // string value = 1; + void clear_value() ; + const std::string& value() const; + template + void set_value(Arg_&& arg, Args_... args); + std::string* mutable_value(); + PROTOBUF_NODISCARD std::string* release_value(); + void set_allocated_value(std::string* value); + + private: + const std::string& _internal_value() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_value( + const std::string& value); + std::string* _internal_mutable_value(); + + public: + // string icon = 2; + void clear_icon() ; + const std::string& icon() const; + template + void set_icon(Arg_&& arg, Args_... args); + std::string* mutable_icon(); + PROTOBUF_NODISCARD std::string* release_icon(); + void set_allocated_icon(std::string* value); + + private: + const std::string& _internal_icon() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_icon( + const std::string& value); + std::string* _internal_mutable_icon(); + + public: + // @@protoc_insertion_point(class_scope:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue) + private: + class _Internal; + friend class ::google::protobuf::internal::TcParser; + static const ::google::protobuf::internal::TcParseTable< + 1, 2, 0, + 86, 2> + _table_; + + static constexpr const void* _raw_default_instance_ = + &_NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue_default_instance_; + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_( + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena); + inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena, const Impl_& from, + const NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& from_msg); + ::google::protobuf::internal::ArenaStringPtr value_; + ::google::protobuf::internal::ArenaStringPtr icon_; + mutable ::google::protobuf::internal::CachedSize _cached_size_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_protobuf_5fnspanel_5fentity_2eproto; +}; +// ------------------------------------------------------------------- + class NSPanelEntityState_Light final : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition:NSPanelEntityState.Light) */ { public: @@ -392,23 +596,23 @@ class NSPanelEntityState_Light final : public ::google::protobuf::Message }; // ------------------------------------------------------------------- -class NSPanelEntityState final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:NSPanelEntityState) */ { +class NSPanelEntityState_Thermostat_ThermostatOption final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:NSPanelEntityState.Thermostat.ThermostatOption) */ { public: - inline NSPanelEntityState() : NSPanelEntityState(nullptr) {} - ~NSPanelEntityState() override; + inline NSPanelEntityState_Thermostat_ThermostatOption() : NSPanelEntityState_Thermostat_ThermostatOption(nullptr) {} + ~NSPanelEntityState_Thermostat_ThermostatOption() override; template - explicit PROTOBUF_CONSTEXPR NSPanelEntityState( + explicit PROTOBUF_CONSTEXPR NSPanelEntityState_Thermostat_ThermostatOption( ::google::protobuf::internal::ConstantInitialized); - inline NSPanelEntityState(const NSPanelEntityState& from) : NSPanelEntityState(nullptr, from) {} - inline NSPanelEntityState(NSPanelEntityState&& from) noexcept - : NSPanelEntityState(nullptr, std::move(from)) {} - inline NSPanelEntityState& operator=(const NSPanelEntityState& from) { + inline NSPanelEntityState_Thermostat_ThermostatOption(const NSPanelEntityState_Thermostat_ThermostatOption& from) : NSPanelEntityState_Thermostat_ThermostatOption(nullptr, from) {} + inline NSPanelEntityState_Thermostat_ThermostatOption(NSPanelEntityState_Thermostat_ThermostatOption&& from) noexcept + : NSPanelEntityState_Thermostat_ThermostatOption(nullptr, std::move(from)) {} + inline NSPanelEntityState_Thermostat_ThermostatOption& operator=(const NSPanelEntityState_Thermostat_ThermostatOption& from) { CopyFrom(from); return *this; } - inline NSPanelEntityState& operator=(NSPanelEntityState&& from) noexcept { + inline NSPanelEntityState_Thermostat_ThermostatOption& operator=(NSPanelEntityState_Thermostat_ThermostatOption&& from) noexcept { if (this == &from) return *this; if (GetArena() == from.GetArena() #ifdef PROTOBUF_FORCE_COPY_IN_MOVE @@ -440,20 +644,16 @@ class NSPanelEntityState final : public ::google::protobuf::Message static const ::google::protobuf::Reflection* GetReflection() { return default_instance().GetMetadata().reflection; } - static const NSPanelEntityState& default_instance() { + static const NSPanelEntityState_Thermostat_ThermostatOption& default_instance() { return *internal_default_instance(); } - enum EntityCase { - kLight = 1, - ENTITY_NOT_SET = 0, - }; - static inline const NSPanelEntityState* internal_default_instance() { - return reinterpret_cast( - &_NSPanelEntityState_default_instance_); + static inline const NSPanelEntityState_Thermostat_ThermostatOption* internal_default_instance() { + return reinterpret_cast( + &_NSPanelEntityState_Thermostat_ThermostatOption_default_instance_); } - static constexpr int kIndexInFileMessages = 1; - friend void swap(NSPanelEntityState& a, NSPanelEntityState& b) { a.Swap(&b); } - inline void Swap(NSPanelEntityState* other) { + static constexpr int kIndexInFileMessages = 2; + friend void swap(NSPanelEntityState_Thermostat_ThermostatOption& a, NSPanelEntityState_Thermostat_ThermostatOption& b) { a.Swap(&b); } + inline void Swap(NSPanelEntityState_Thermostat_ThermostatOption* other) { if (other == this) return; #ifdef PROTOBUF_FORCE_COPY_IN_SWAP if (GetArena() != nullptr && GetArena() == other->GetArena()) { @@ -465,7 +665,7 @@ class NSPanelEntityState final : public ::google::protobuf::Message ::google::protobuf::internal::GenericSwap(this, other); } } - void UnsafeArenaSwap(NSPanelEntityState* other) { + void UnsafeArenaSwap(NSPanelEntityState_Thermostat_ThermostatOption* other) { if (other == this) return; ABSL_DCHECK(GetArena() == other->GetArena()); InternalSwap(other); @@ -473,13 +673,13 @@ class NSPanelEntityState final : public ::google::protobuf::Message // implements Message ---------------------------------------------- - NSPanelEntityState* New(::google::protobuf::Arena* arena = nullptr) const final { - return ::google::protobuf::Message::DefaultConstruct(arena); + NSPanelEntityState_Thermostat_ThermostatOption* New(::google::protobuf::Arena* arena = nullptr) const final { + return ::google::protobuf::Message::DefaultConstruct(arena); } using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const NSPanelEntityState& from); + void CopyFrom(const NSPanelEntityState_Thermostat_ThermostatOption& from); using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const NSPanelEntityState& from) { NSPanelEntityState::MergeImpl(*this, from); } + void MergeFrom(const NSPanelEntityState_Thermostat_ThermostatOption& from) { NSPanelEntityState_Thermostat_ThermostatOption::MergeImpl(*this, from); } private: static void MergeImpl( @@ -500,16 +700,16 @@ class NSPanelEntityState final : public ::google::protobuf::Message private: void SharedCtor(::google::protobuf::Arena* arena); void SharedDtor(); - void InternalSwap(NSPanelEntityState* other); + void InternalSwap(NSPanelEntityState_Thermostat_ThermostatOption* other); private: friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "NSPanelEntityState"; } + static ::absl::string_view FullMessageName() { return "NSPanelEntityState.Thermostat.ThermostatOption"; } protected: - explicit NSPanelEntityState(::google::protobuf::Arena* arena); - NSPanelEntityState(::google::protobuf::Arena* arena, const NSPanelEntityState& from); - NSPanelEntityState(::google::protobuf::Arena* arena, NSPanelEntityState&& from) noexcept - : NSPanelEntityState(arena) { + explicit NSPanelEntityState_Thermostat_ThermostatOption(::google::protobuf::Arena* arena); + NSPanelEntityState_Thermostat_ThermostatOption(::google::protobuf::Arena* arena, const NSPanelEntityState_Thermostat_ThermostatOption& from); + NSPanelEntityState_Thermostat_ThermostatOption(::google::protobuf::Arena* arena, NSPanelEntityState_Thermostat_ThermostatOption&& from) noexcept + : NSPanelEntityState_Thermostat_ThermostatOption(arena) { *this = ::std::move(from); } const ::google::protobuf::Message::ClassData* GetClassData() const final; @@ -517,47 +717,91 @@ class NSPanelEntityState final : public ::google::protobuf::Message public: ::google::protobuf::Metadata GetMetadata() const; // nested types ---------------------------------------------------- - using Light = NSPanelEntityState_Light; + using ThermostatOptionValue = NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue; // accessors ------------------------------------------------------- enum : int { - kLightFieldNumber = 1, + kOptionsFieldNumber = 4, + kNameFieldNumber = 1, + kCurrentValueFieldNumber = 2, + kCurrentIconFieldNumber = 3, }; - // .NSPanelEntityState.Light light = 1; - bool has_light() const; + // repeated .NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue options = 4; + int options_size() const; private: - bool _internal_has_light() const; + int _internal_options_size() const; public: - void clear_light() ; - const ::NSPanelEntityState_Light& light() const; - PROTOBUF_NODISCARD ::NSPanelEntityState_Light* release_light(); - ::NSPanelEntityState_Light* mutable_light(); - void set_allocated_light(::NSPanelEntityState_Light* value); - void unsafe_arena_set_allocated_light(::NSPanelEntityState_Light* value); - ::NSPanelEntityState_Light* unsafe_arena_release_light(); + void clear_options() ; + ::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue* mutable_options(int index); + ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue>* mutable_options(); private: - const ::NSPanelEntityState_Light& _internal_light() const; - ::NSPanelEntityState_Light* _internal_mutable_light(); + const ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue>& _internal_options() const; + ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue>* _internal_mutable_options(); + public: + const ::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& options(int index) const; + ::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue* add_options(); + const ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue>& options() const; + // string name = 1; + void clear_name() ; + const std::string& name() const; + template + void set_name(Arg_&& arg, Args_... args); + std::string* mutable_name(); + PROTOBUF_NODISCARD std::string* release_name(); + void set_allocated_name(std::string* value); + + private: + const std::string& _internal_name() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_name( + const std::string& value); + std::string* _internal_mutable_name(); public: - void clear_entity(); - EntityCase entity_case() const; - // @@protoc_insertion_point(class_scope:NSPanelEntityState) + // string current_value = 2; + void clear_current_value() ; + const std::string& current_value() const; + template + void set_current_value(Arg_&& arg, Args_... args); + std::string* mutable_current_value(); + PROTOBUF_NODISCARD std::string* release_current_value(); + void set_allocated_current_value(std::string* value); + + private: + const std::string& _internal_current_value() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_current_value( + const std::string& value); + std::string* _internal_mutable_current_value(); + + public: + // string current_icon = 3; + void clear_current_icon() ; + const std::string& current_icon() const; + template + void set_current_icon(Arg_&& arg, Args_... args); + std::string* mutable_current_icon(); + PROTOBUF_NODISCARD std::string* release_current_icon(); + void set_allocated_current_icon(std::string* value); + + private: + const std::string& _internal_current_icon() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_current_icon( + const std::string& value); + std::string* _internal_mutable_current_icon(); + + public: + // @@protoc_insertion_point(class_scope:NSPanelEntityState.Thermostat.ThermostatOption) private: class _Internal; - void set_has_light(); - inline bool has_entity() const; - inline void clear_has_entity(); friend class ::google::protobuf::internal::TcParser; static const ::google::protobuf::internal::TcParseTable< - 0, 1, 1, - 0, 2> + 2, 4, 1, + 84, 2> _table_; static constexpr const void* _raw_default_instance_ = - &_NSPanelEntityState_default_instance_; + &_NSPanelEntityState_Thermostat_ThermostatOption_default_instance_; friend class ::google::protobuf::MessageLite; friend class ::google::protobuf::Arena; @@ -572,98 +816,1109 @@ class NSPanelEntityState final : public ::google::protobuf::Message ::google::protobuf::Arena* arena); inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, ::google::protobuf::Arena* arena, const Impl_& from, - const NSPanelEntityState& from_msg); - union EntityUnion { - constexpr EntityUnion() : _constinit_{} {} - ::google::protobuf::internal::ConstantInitialized _constinit_; - ::NSPanelEntityState_Light* light_; - } entity_; + const NSPanelEntityState_Thermostat_ThermostatOption& from_msg); + ::google::protobuf::RepeatedPtrField< ::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue > options_; + ::google::protobuf::internal::ArenaStringPtr name_; + ::google::protobuf::internal::ArenaStringPtr current_value_; + ::google::protobuf::internal::ArenaStringPtr current_icon_; mutable ::google::protobuf::internal::CachedSize _cached_size_; - ::uint32_t _oneof_case_[1]; PROTOBUF_TSAN_DECLARE_MEMBER }; union { Impl_ _impl_; }; friend struct ::TableStruct_protobuf_5fnspanel_5fentity_2eproto; }; +// ------------------------------------------------------------------- -// =================================================================== - +class NSPanelEntityState_Thermostat final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:NSPanelEntityState.Thermostat) */ { + public: + inline NSPanelEntityState_Thermostat() : NSPanelEntityState_Thermostat(nullptr) {} + ~NSPanelEntityState_Thermostat() override; + template + explicit PROTOBUF_CONSTEXPR NSPanelEntityState_Thermostat( + ::google::protobuf::internal::ConstantInitialized); + inline NSPanelEntityState_Thermostat(const NSPanelEntityState_Thermostat& from) : NSPanelEntityState_Thermostat(nullptr, from) {} + inline NSPanelEntityState_Thermostat(NSPanelEntityState_Thermostat&& from) noexcept + : NSPanelEntityState_Thermostat(nullptr, std::move(from)) {} + inline NSPanelEntityState_Thermostat& operator=(const NSPanelEntityState_Thermostat& from) { + CopyFrom(from); + return *this; + } + inline NSPanelEntityState_Thermostat& operator=(NSPanelEntityState_Thermostat&& from) noexcept { + if (this == &from) return *this; + if (GetArena() == from.GetArena() +#ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetArena() != nullptr +#endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + static const ::google::protobuf::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::google::protobuf::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::google::protobuf::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const NSPanelEntityState_Thermostat& default_instance() { + return *internal_default_instance(); + } + static inline const NSPanelEntityState_Thermostat* internal_default_instance() { + return reinterpret_cast( + &_NSPanelEntityState_Thermostat_default_instance_); + } + static constexpr int kIndexInFileMessages = 3; + friend void swap(NSPanelEntityState_Thermostat& a, NSPanelEntityState_Thermostat& b) { a.Swap(&b); } + inline void Swap(NSPanelEntityState_Thermostat* other) { + if (other == this) return; +#ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetArena() != nullptr && GetArena() == other->GetArena()) { +#else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetArena() == other->GetArena()) { +#endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(NSPanelEntityState_Thermostat* other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + NSPanelEntityState_Thermostat* New(::google::protobuf::Arena* arena = nullptr) const final { + return ::google::protobuf::Message::DefaultConstruct(arena); + } + using ::google::protobuf::Message::CopyFrom; + void CopyFrom(const NSPanelEntityState_Thermostat& from); + using ::google::protobuf::Message::MergeFrom; + void MergeFrom(const NSPanelEntityState_Thermostat& from) { NSPanelEntityState_Thermostat::MergeImpl(*this, from); } + + private: + static void MergeImpl( + ::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() final; + ::size_t ByteSizeLong() const final; + ::uint8_t* _InternalSerialize( + ::uint8_t* target, + ::google::protobuf::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::google::protobuf::Arena* arena); + void SharedDtor(); + void InternalSwap(NSPanelEntityState_Thermostat* other); + private: + friend class ::google::protobuf::internal::AnyMetadata; + static ::absl::string_view FullMessageName() { return "NSPanelEntityState.Thermostat"; } + + protected: + explicit NSPanelEntityState_Thermostat(::google::protobuf::Arena* arena); + NSPanelEntityState_Thermostat(::google::protobuf::Arena* arena, const NSPanelEntityState_Thermostat& from); + NSPanelEntityState_Thermostat(::google::protobuf::Arena* arena, NSPanelEntityState_Thermostat&& from) noexcept + : NSPanelEntityState_Thermostat(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::Message::ClassData* GetClassData() const final; + + public: + ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + using ThermostatOption = NSPanelEntityState_Thermostat_ThermostatOption; + + // accessors ------------------------------------------------------- + enum : int { + kOptionsFieldNumber = 7, + kNameFieldNumber = 2, + kThermostatIdFieldNumber = 1, + kCurrentTemperatureFieldNumber = 3, + kHasCurrentTemperatureFieldNumber = 4, + kSetTemperatureFieldNumber = 5, + kStepSizeFieldNumber = 6, + }; + // repeated .NSPanelEntityState.Thermostat.ThermostatOption options = 7; + int options_size() const; + private: + int _internal_options_size() const; + + public: + void clear_options() ; + ::NSPanelEntityState_Thermostat_ThermostatOption* mutable_options(int index); + ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption>* mutable_options(); + + private: + const ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption>& _internal_options() const; + ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption>* _internal_mutable_options(); + public: + const ::NSPanelEntityState_Thermostat_ThermostatOption& options(int index) const; + ::NSPanelEntityState_Thermostat_ThermostatOption* add_options(); + const ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption>& options() const; + // string name = 2; + void clear_name() ; + const std::string& name() const; + template + void set_name(Arg_&& arg, Args_... args); + std::string* mutable_name(); + PROTOBUF_NODISCARD std::string* release_name(); + void set_allocated_name(std::string* value); + + private: + const std::string& _internal_name() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_name( + const std::string& value); + std::string* _internal_mutable_name(); + + public: + // int32 thermostat_id = 1; + void clear_thermostat_id() ; + ::int32_t thermostat_id() const; + void set_thermostat_id(::int32_t value); + + private: + ::int32_t _internal_thermostat_id() const; + void _internal_set_thermostat_id(::int32_t value); + + public: + // float current_temperature = 3; + void clear_current_temperature() ; + float current_temperature() const; + void set_current_temperature(float value); + + private: + float _internal_current_temperature() const; + void _internal_set_current_temperature(float value); + + public: + // bool has_current_temperature = 4; + void clear_has_current_temperature() ; + bool has_current_temperature() const; + void set_has_current_temperature(bool value); + + private: + bool _internal_has_current_temperature() const; + void _internal_set_has_current_temperature(bool value); + + public: + // float set_temperature = 5; + void clear_set_temperature() ; + float set_temperature() const; + void set_set_temperature(float value); + + private: + float _internal_set_temperature() const; + void _internal_set_set_temperature(float value); + + public: + // float step_size = 6; + void clear_step_size() ; + float step_size() const; + void set_step_size(float value); + + private: + float _internal_step_size() const; + void _internal_set_step_size(float value); + + public: + // @@protoc_insertion_point(class_scope:NSPanelEntityState.Thermostat) + private: + class _Internal; + friend class ::google::protobuf::internal::TcParser; + static const ::google::protobuf::internal::TcParseTable< + 3, 7, 1, + 42, 2> + _table_; + + static constexpr const void* _raw_default_instance_ = + &_NSPanelEntityState_Thermostat_default_instance_; + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_( + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena); + inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena, const Impl_& from, + const NSPanelEntityState_Thermostat& from_msg); + ::google::protobuf::RepeatedPtrField< ::NSPanelEntityState_Thermostat_ThermostatOption > options_; + ::google::protobuf::internal::ArenaStringPtr name_; + ::int32_t thermostat_id_; + float current_temperature_; + bool has_current_temperature_; + float set_temperature_; + float step_size_; + mutable ::google::protobuf::internal::CachedSize _cached_size_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_protobuf_5fnspanel_5fentity_2eproto; +}; +// ------------------------------------------------------------------- + +class NSPanelEntityState final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:NSPanelEntityState) */ { + public: + inline NSPanelEntityState() : NSPanelEntityState(nullptr) {} + ~NSPanelEntityState() override; + template + explicit PROTOBUF_CONSTEXPR NSPanelEntityState( + ::google::protobuf::internal::ConstantInitialized); + + inline NSPanelEntityState(const NSPanelEntityState& from) : NSPanelEntityState(nullptr, from) {} + inline NSPanelEntityState(NSPanelEntityState&& from) noexcept + : NSPanelEntityState(nullptr, std::move(from)) {} + inline NSPanelEntityState& operator=(const NSPanelEntityState& from) { + CopyFrom(from); + return *this; + } + inline NSPanelEntityState& operator=(NSPanelEntityState&& from) noexcept { + if (this == &from) return *this; + if (GetArena() == from.GetArena() +#ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetArena() != nullptr +#endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + static const ::google::protobuf::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::google::protobuf::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::google::protobuf::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const NSPanelEntityState& default_instance() { + return *internal_default_instance(); + } + enum EntityCase { + kLight = 1, + kThermostat = 2, + ENTITY_NOT_SET = 0, + }; + static inline const NSPanelEntityState* internal_default_instance() { + return reinterpret_cast( + &_NSPanelEntityState_default_instance_); + } + static constexpr int kIndexInFileMessages = 4; + friend void swap(NSPanelEntityState& a, NSPanelEntityState& b) { a.Swap(&b); } + inline void Swap(NSPanelEntityState* other) { + if (other == this) return; +#ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetArena() != nullptr && GetArena() == other->GetArena()) { +#else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetArena() == other->GetArena()) { +#endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(NSPanelEntityState* other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + NSPanelEntityState* New(::google::protobuf::Arena* arena = nullptr) const final { + return ::google::protobuf::Message::DefaultConstruct(arena); + } + using ::google::protobuf::Message::CopyFrom; + void CopyFrom(const NSPanelEntityState& from); + using ::google::protobuf::Message::MergeFrom; + void MergeFrom(const NSPanelEntityState& from) { NSPanelEntityState::MergeImpl(*this, from); } + + private: + static void MergeImpl( + ::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() final; + ::size_t ByteSizeLong() const final; + ::uint8_t* _InternalSerialize( + ::uint8_t* target, + ::google::protobuf::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::google::protobuf::Arena* arena); + void SharedDtor(); + void InternalSwap(NSPanelEntityState* other); + private: + friend class ::google::protobuf::internal::AnyMetadata; + static ::absl::string_view FullMessageName() { return "NSPanelEntityState"; } + + protected: + explicit NSPanelEntityState(::google::protobuf::Arena* arena); + NSPanelEntityState(::google::protobuf::Arena* arena, const NSPanelEntityState& from); + NSPanelEntityState(::google::protobuf::Arena* arena, NSPanelEntityState&& from) noexcept + : NSPanelEntityState(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::Message::ClassData* GetClassData() const final; + + public: + ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + using Light = NSPanelEntityState_Light; + using Thermostat = NSPanelEntityState_Thermostat; + + // accessors ------------------------------------------------------- + enum : int { + kLightFieldNumber = 1, + kThermostatFieldNumber = 2, + }; + // .NSPanelEntityState.Light light = 1; + bool has_light() const; + private: + bool _internal_has_light() const; + + public: + void clear_light() ; + const ::NSPanelEntityState_Light& light() const; + PROTOBUF_NODISCARD ::NSPanelEntityState_Light* release_light(); + ::NSPanelEntityState_Light* mutable_light(); + void set_allocated_light(::NSPanelEntityState_Light* value); + void unsafe_arena_set_allocated_light(::NSPanelEntityState_Light* value); + ::NSPanelEntityState_Light* unsafe_arena_release_light(); + + private: + const ::NSPanelEntityState_Light& _internal_light() const; + ::NSPanelEntityState_Light* _internal_mutable_light(); + + public: + // .NSPanelEntityState.Thermostat thermostat = 2; + bool has_thermostat() const; + private: + bool _internal_has_thermostat() const; + + public: + void clear_thermostat() ; + const ::NSPanelEntityState_Thermostat& thermostat() const; + PROTOBUF_NODISCARD ::NSPanelEntityState_Thermostat* release_thermostat(); + ::NSPanelEntityState_Thermostat* mutable_thermostat(); + void set_allocated_thermostat(::NSPanelEntityState_Thermostat* value); + void unsafe_arena_set_allocated_thermostat(::NSPanelEntityState_Thermostat* value); + ::NSPanelEntityState_Thermostat* unsafe_arena_release_thermostat(); + + private: + const ::NSPanelEntityState_Thermostat& _internal_thermostat() const; + ::NSPanelEntityState_Thermostat* _internal_mutable_thermostat(); + + public: + void clear_entity(); + EntityCase entity_case() const; + // @@protoc_insertion_point(class_scope:NSPanelEntityState) + private: + class _Internal; + void set_has_light(); + void set_has_thermostat(); + inline bool has_entity() const; + inline void clear_has_entity(); + friend class ::google::protobuf::internal::TcParser; + static const ::google::protobuf::internal::TcParseTable< + 0, 2, 2, + 0, 2> + _table_; + + static constexpr const void* _raw_default_instance_ = + &_NSPanelEntityState_default_instance_; + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_( + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena); + inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* arena, const Impl_& from, + const NSPanelEntityState& from_msg); + union EntityUnion { + constexpr EntityUnion() : _constinit_{} {} + ::google::protobuf::internal::ConstantInitialized _constinit_; + ::NSPanelEntityState_Light* light_; + ::NSPanelEntityState_Thermostat* thermostat_; + } entity_; + mutable ::google::protobuf::internal::CachedSize _cached_size_; + ::uint32_t _oneof_case_[1]; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_protobuf_5fnspanel_5fentity_2eproto; +}; // =================================================================== -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstrict-aliasing" -#endif // __GNUC__ + + +// =================================================================== + + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif // __GNUC__ +// ------------------------------------------------------------------- + +// NSPanelEntityState_Light + +// int32 light_id = 1; +inline void NSPanelEntityState_Light::clear_light_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.light_id_ = 0; +} +inline ::int32_t NSPanelEntityState_Light::light_id() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.light_id) + return _internal_light_id(); +} +inline void NSPanelEntityState_Light::set_light_id(::int32_t value) { + _internal_set_light_id(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.light_id) +} +inline ::int32_t NSPanelEntityState_Light::_internal_light_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.light_id_; +} +inline void NSPanelEntityState_Light::_internal_set_light_id(::int32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.light_id_ = value; +} + +// string name = 2; +inline void NSPanelEntityState_Light::clear_name() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.name_.ClearToEmpty(); +} +inline const std::string& NSPanelEntityState_Light::name() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.name) + return _internal_name(); +} +template +inline PROTOBUF_ALWAYS_INLINE void NSPanelEntityState_Light::set_name(Arg_&& arg, + Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.name_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.name) +} +inline std::string* NSPanelEntityState_Light::mutable_name() ABSL_ATTRIBUTE_LIFETIME_BOUND { + std::string* _s = _internal_mutable_name(); + // @@protoc_insertion_point(field_mutable:NSPanelEntityState.Light.name) + return _s; +} +inline const std::string& NSPanelEntityState_Light::_internal_name() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.name_.Get(); +} +inline void NSPanelEntityState_Light::_internal_set_name(const std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.name_.Set(value, GetArena()); +} +inline std::string* NSPanelEntityState_Light::_internal_mutable_name() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.name_.Mutable( GetArena()); +} +inline std::string* NSPanelEntityState_Light::release_name() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:NSPanelEntityState.Light.name) + return _impl_.name_.Release(); +} +inline void NSPanelEntityState_Light::set_allocated_name(std::string* value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.name_.SetAllocated(value, GetArena()); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArena()); + } + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:NSPanelEntityState.Light.name) +} + +// int32 brightness = 3; +inline void NSPanelEntityState_Light::clear_brightness() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.brightness_ = 0; +} +inline ::int32_t NSPanelEntityState_Light::brightness() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.brightness) + return _internal_brightness(); +} +inline void NSPanelEntityState_Light::set_brightness(::int32_t value) { + _internal_set_brightness(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.brightness) +} +inline ::int32_t NSPanelEntityState_Light::_internal_brightness() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.brightness_; +} +inline void NSPanelEntityState_Light::_internal_set_brightness(::int32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.brightness_ = value; +} + +// int32 color_temp = 4; +inline void NSPanelEntityState_Light::clear_color_temp() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.color_temp_ = 0; +} +inline ::int32_t NSPanelEntityState_Light::color_temp() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.color_temp) + return _internal_color_temp(); +} +inline void NSPanelEntityState_Light::set_color_temp(::int32_t value) { + _internal_set_color_temp(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.color_temp) +} +inline ::int32_t NSPanelEntityState_Light::_internal_color_temp() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.color_temp_; +} +inline void NSPanelEntityState_Light::_internal_set_color_temp(::int32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.color_temp_ = value; +} + +// int32 hue = 5; +inline void NSPanelEntityState_Light::clear_hue() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.hue_ = 0; +} +inline ::int32_t NSPanelEntityState_Light::hue() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.hue) + return _internal_hue(); +} +inline void NSPanelEntityState_Light::set_hue(::int32_t value) { + _internal_set_hue(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.hue) +} +inline ::int32_t NSPanelEntityState_Light::_internal_hue() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.hue_; +} +inline void NSPanelEntityState_Light::_internal_set_hue(::int32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.hue_ = value; +} + +// int32 saturation = 6; +inline void NSPanelEntityState_Light::clear_saturation() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.saturation_ = 0; +} +inline ::int32_t NSPanelEntityState_Light::saturation() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.saturation) + return _internal_saturation(); +} +inline void NSPanelEntityState_Light::set_saturation(::int32_t value) { + _internal_set_saturation(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.saturation) +} +inline ::int32_t NSPanelEntityState_Light::_internal_saturation() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.saturation_; +} +inline void NSPanelEntityState_Light::_internal_set_saturation(::int32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.saturation_ = value; +} + +// bool can_color_temp = 7; +inline void NSPanelEntityState_Light::clear_can_color_temp() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.can_color_temp_ = false; +} +inline bool NSPanelEntityState_Light::can_color_temp() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.can_color_temp) + return _internal_can_color_temp(); +} +inline void NSPanelEntityState_Light::set_can_color_temp(bool value) { + _internal_set_can_color_temp(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.can_color_temp) +} +inline bool NSPanelEntityState_Light::_internal_can_color_temp() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.can_color_temp_; +} +inline void NSPanelEntityState_Light::_internal_set_can_color_temp(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.can_color_temp_ = value; +} + +// bool can_color = 8; +inline void NSPanelEntityState_Light::clear_can_color() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.can_color_ = false; +} +inline bool NSPanelEntityState_Light::can_color() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.can_color) + return _internal_can_color(); +} +inline void NSPanelEntityState_Light::set_can_color(bool value) { + _internal_set_can_color(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.can_color) +} +inline bool NSPanelEntityState_Light::_internal_can_color() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.can_color_; +} +inline void NSPanelEntityState_Light::_internal_set_can_color(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.can_color_ = value; +} + +// .NSPanelEntityState.Light.LightMode current_light_mode = 9; +inline void NSPanelEntityState_Light::clear_current_light_mode() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.current_light_mode_ = 0; +} +inline ::NSPanelEntityState_Light_LightMode NSPanelEntityState_Light::current_light_mode() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.current_light_mode) + return _internal_current_light_mode(); +} +inline void NSPanelEntityState_Light::set_current_light_mode(::NSPanelEntityState_Light_LightMode value) { + _internal_set_current_light_mode(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.current_light_mode) +} +inline ::NSPanelEntityState_Light_LightMode NSPanelEntityState_Light::_internal_current_light_mode() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return static_cast<::NSPanelEntityState_Light_LightMode>(_impl_.current_light_mode_); +} +inline void NSPanelEntityState_Light::_internal_set_current_light_mode(::NSPanelEntityState_Light_LightMode value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.current_light_mode_ = value; +} + +// ------------------------------------------------------------------- + +// NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue + +// string value = 1; +inline void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::clear_value() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.value_.ClearToEmpty(); +} +inline const std::string& NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::value() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue.value) + return _internal_value(); +} +template +inline PROTOBUF_ALWAYS_INLINE void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::set_value(Arg_&& arg, + Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.value_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue.value) +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::mutable_value() ABSL_ATTRIBUTE_LIFETIME_BOUND { + std::string* _s = _internal_mutable_value(); + // @@protoc_insertion_point(field_mutable:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue.value) + return _s; +} +inline const std::string& NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::_internal_value() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.value_.Get(); +} +inline void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::_internal_set_value(const std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.value_.Set(value, GetArena()); +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::_internal_mutable_value() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.value_.Mutable( GetArena()); +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::release_value() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue.value) + return _impl_.value_.Release(); +} +inline void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::set_allocated_value(std::string* value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.value_.SetAllocated(value, GetArena()); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.value_.IsDefault()) { + _impl_.value_.Set("", GetArena()); + } + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue.value) +} + +// string icon = 2; +inline void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::clear_icon() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.icon_.ClearToEmpty(); +} +inline const std::string& NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::icon() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue.icon) + return _internal_icon(); +} +template +inline PROTOBUF_ALWAYS_INLINE void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::set_icon(Arg_&& arg, + Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.icon_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue.icon) +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::mutable_icon() ABSL_ATTRIBUTE_LIFETIME_BOUND { + std::string* _s = _internal_mutable_icon(); + // @@protoc_insertion_point(field_mutable:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue.icon) + return _s; +} +inline const std::string& NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::_internal_icon() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.icon_.Get(); +} +inline void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::_internal_set_icon(const std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.icon_.Set(value, GetArena()); +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::_internal_mutable_icon() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.icon_.Mutable( GetArena()); +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::release_icon() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue.icon) + return _impl_.icon_.Release(); +} +inline void NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue::set_allocated_icon(std::string* value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.icon_.SetAllocated(value, GetArena()); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.icon_.IsDefault()) { + _impl_.icon_.Set("", GetArena()); + } + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue.icon) +} + +// ------------------------------------------------------------------- + +// NSPanelEntityState_Thermostat_ThermostatOption + +// string name = 1; +inline void NSPanelEntityState_Thermostat_ThermostatOption::clear_name() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.name_.ClearToEmpty(); +} +inline const std::string& NSPanelEntityState_Thermostat_ThermostatOption::name() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.ThermostatOption.name) + return _internal_name(); +} +template +inline PROTOBUF_ALWAYS_INLINE void NSPanelEntityState_Thermostat_ThermostatOption::set_name(Arg_&& arg, + Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.name_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Thermostat.ThermostatOption.name) +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption::mutable_name() ABSL_ATTRIBUTE_LIFETIME_BOUND { + std::string* _s = _internal_mutable_name(); + // @@protoc_insertion_point(field_mutable:NSPanelEntityState.Thermostat.ThermostatOption.name) + return _s; +} +inline const std::string& NSPanelEntityState_Thermostat_ThermostatOption::_internal_name() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.name_.Get(); +} +inline void NSPanelEntityState_Thermostat_ThermostatOption::_internal_set_name(const std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.name_.Set(value, GetArena()); +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption::_internal_mutable_name() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.name_.Mutable( GetArena()); +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption::release_name() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:NSPanelEntityState.Thermostat.ThermostatOption.name) + return _impl_.name_.Release(); +} +inline void NSPanelEntityState_Thermostat_ThermostatOption::set_allocated_name(std::string* value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.name_.SetAllocated(value, GetArena()); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArena()); + } + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:NSPanelEntityState.Thermostat.ThermostatOption.name) +} + +// string current_value = 2; +inline void NSPanelEntityState_Thermostat_ThermostatOption::clear_current_value() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.current_value_.ClearToEmpty(); +} +inline const std::string& NSPanelEntityState_Thermostat_ThermostatOption::current_value() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.ThermostatOption.current_value) + return _internal_current_value(); +} +template +inline PROTOBUF_ALWAYS_INLINE void NSPanelEntityState_Thermostat_ThermostatOption::set_current_value(Arg_&& arg, + Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.current_value_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Thermostat.ThermostatOption.current_value) +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption::mutable_current_value() ABSL_ATTRIBUTE_LIFETIME_BOUND { + std::string* _s = _internal_mutable_current_value(); + // @@protoc_insertion_point(field_mutable:NSPanelEntityState.Thermostat.ThermostatOption.current_value) + return _s; +} +inline const std::string& NSPanelEntityState_Thermostat_ThermostatOption::_internal_current_value() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.current_value_.Get(); +} +inline void NSPanelEntityState_Thermostat_ThermostatOption::_internal_set_current_value(const std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.current_value_.Set(value, GetArena()); +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption::_internal_mutable_current_value() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.current_value_.Mutable( GetArena()); +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption::release_current_value() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:NSPanelEntityState.Thermostat.ThermostatOption.current_value) + return _impl_.current_value_.Release(); +} +inline void NSPanelEntityState_Thermostat_ThermostatOption::set_allocated_current_value(std::string* value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.current_value_.SetAllocated(value, GetArena()); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.current_value_.IsDefault()) { + _impl_.current_value_.Set("", GetArena()); + } + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:NSPanelEntityState.Thermostat.ThermostatOption.current_value) +} + +// string current_icon = 3; +inline void NSPanelEntityState_Thermostat_ThermostatOption::clear_current_icon() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.current_icon_.ClearToEmpty(); +} +inline const std::string& NSPanelEntityState_Thermostat_ThermostatOption::current_icon() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.ThermostatOption.current_icon) + return _internal_current_icon(); +} +template +inline PROTOBUF_ALWAYS_INLINE void NSPanelEntityState_Thermostat_ThermostatOption::set_current_icon(Arg_&& arg, + Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.current_icon_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Thermostat.ThermostatOption.current_icon) +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption::mutable_current_icon() ABSL_ATTRIBUTE_LIFETIME_BOUND { + std::string* _s = _internal_mutable_current_icon(); + // @@protoc_insertion_point(field_mutable:NSPanelEntityState.Thermostat.ThermostatOption.current_icon) + return _s; +} +inline const std::string& NSPanelEntityState_Thermostat_ThermostatOption::_internal_current_icon() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.current_icon_.Get(); +} +inline void NSPanelEntityState_Thermostat_ThermostatOption::_internal_set_current_icon(const std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.current_icon_.Set(value, GetArena()); +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption::_internal_mutable_current_icon() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.current_icon_.Mutable( GetArena()); +} +inline std::string* NSPanelEntityState_Thermostat_ThermostatOption::release_current_icon() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:NSPanelEntityState.Thermostat.ThermostatOption.current_icon) + return _impl_.current_icon_.Release(); +} +inline void NSPanelEntityState_Thermostat_ThermostatOption::set_allocated_current_icon(std::string* value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.current_icon_.SetAllocated(value, GetArena()); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.current_icon_.IsDefault()) { + _impl_.current_icon_.Set("", GetArena()); + } + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:NSPanelEntityState.Thermostat.ThermostatOption.current_icon) +} + +// repeated .NSPanelEntityState.Thermostat.ThermostatOption.ThermostatOptionValue options = 4; +inline int NSPanelEntityState_Thermostat_ThermostatOption::_internal_options_size() const { + return _internal_options().size(); +} +inline int NSPanelEntityState_Thermostat_ThermostatOption::options_size() const { + return _internal_options_size(); +} +inline void NSPanelEntityState_Thermostat_ThermostatOption::clear_options() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.options_.Clear(); +} +inline ::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue* NSPanelEntityState_Thermostat_ThermostatOption::mutable_options(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:NSPanelEntityState.Thermostat.ThermostatOption.options) + return _internal_mutable_options()->Mutable(index); +} +inline ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue>* NSPanelEntityState_Thermostat_ThermostatOption::mutable_options() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable_list:NSPanelEntityState.Thermostat.ThermostatOption.options) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_options(); +} +inline const ::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue& NSPanelEntityState_Thermostat_ThermostatOption::options(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.ThermostatOption.options) + return _internal_options().Get(index); +} +inline ::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue* NSPanelEntityState_Thermostat_ThermostatOption::add_options() ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue* _add = _internal_mutable_options()->Add(); + // @@protoc_insertion_point(field_add:NSPanelEntityState.Thermostat.ThermostatOption.options) + return _add; +} +inline const ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue>& NSPanelEntityState_Thermostat_ThermostatOption::options() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:NSPanelEntityState.Thermostat.ThermostatOption.options) + return _internal_options(); +} +inline const ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue>& +NSPanelEntityState_Thermostat_ThermostatOption::_internal_options() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.options_; +} +inline ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption_ThermostatOptionValue>* +NSPanelEntityState_Thermostat_ThermostatOption::_internal_mutable_options() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.options_; +} + // ------------------------------------------------------------------- -// NSPanelEntityState_Light +// NSPanelEntityState_Thermostat -// int32 light_id = 1; -inline void NSPanelEntityState_Light::clear_light_id() { +// int32 thermostat_id = 1; +inline void NSPanelEntityState_Thermostat::clear_thermostat_id() { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.light_id_ = 0; + _impl_.thermostat_id_ = 0; } -inline ::int32_t NSPanelEntityState_Light::light_id() const { - // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.light_id) - return _internal_light_id(); +inline ::int32_t NSPanelEntityState_Thermostat::thermostat_id() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.thermostat_id) + return _internal_thermostat_id(); } -inline void NSPanelEntityState_Light::set_light_id(::int32_t value) { - _internal_set_light_id(value); - // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.light_id) +inline void NSPanelEntityState_Thermostat::set_thermostat_id(::int32_t value) { + _internal_set_thermostat_id(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Thermostat.thermostat_id) } -inline ::int32_t NSPanelEntityState_Light::_internal_light_id() const { +inline ::int32_t NSPanelEntityState_Thermostat::_internal_thermostat_id() const { ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.light_id_; + return _impl_.thermostat_id_; } -inline void NSPanelEntityState_Light::_internal_set_light_id(::int32_t value) { +inline void NSPanelEntityState_Thermostat::_internal_set_thermostat_id(::int32_t value) { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.light_id_ = value; + _impl_.thermostat_id_ = value; } // string name = 2; -inline void NSPanelEntityState_Light::clear_name() { +inline void NSPanelEntityState_Thermostat::clear_name() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.name_.ClearToEmpty(); } -inline const std::string& NSPanelEntityState_Light::name() const +inline const std::string& NSPanelEntityState_Thermostat::name() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.name) + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.name) return _internal_name(); } template -inline PROTOBUF_ALWAYS_INLINE void NSPanelEntityState_Light::set_name(Arg_&& arg, +inline PROTOBUF_ALWAYS_INLINE void NSPanelEntityState_Thermostat::set_name(Arg_&& arg, Args_... args) { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.name_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.name) + // @@protoc_insertion_point(field_set:NSPanelEntityState.Thermostat.name) } -inline std::string* NSPanelEntityState_Light::mutable_name() ABSL_ATTRIBUTE_LIFETIME_BOUND { +inline std::string* NSPanelEntityState_Thermostat::mutable_name() ABSL_ATTRIBUTE_LIFETIME_BOUND { std::string* _s = _internal_mutable_name(); - // @@protoc_insertion_point(field_mutable:NSPanelEntityState.Light.name) + // @@protoc_insertion_point(field_mutable:NSPanelEntityState.Thermostat.name) return _s; } -inline const std::string& NSPanelEntityState_Light::_internal_name() const { +inline const std::string& NSPanelEntityState_Thermostat::_internal_name() const { ::google::protobuf::internal::TSanRead(&_impl_); return _impl_.name_.Get(); } -inline void NSPanelEntityState_Light::_internal_set_name(const std::string& value) { +inline void NSPanelEntityState_Thermostat::_internal_set_name(const std::string& value) { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.name_.Set(value, GetArena()); } -inline std::string* NSPanelEntityState_Light::_internal_mutable_name() { +inline std::string* NSPanelEntityState_Thermostat::_internal_mutable_name() { ::google::protobuf::internal::TSanWrite(&_impl_); return _impl_.name_.Mutable( GetArena()); } -inline std::string* NSPanelEntityState_Light::release_name() { +inline std::string* NSPanelEntityState_Thermostat::release_name() { ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:NSPanelEntityState.Light.name) + // @@protoc_insertion_point(field_release:NSPanelEntityState.Thermostat.name) return _impl_.name_.Release(); } -inline void NSPanelEntityState_Light::set_allocated_name(std::string* value) { +inline void NSPanelEntityState_Thermostat::set_allocated_name(std::string* value) { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.name_.SetAllocated(value, GetArena()); #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING @@ -671,161 +1926,144 @@ inline void NSPanelEntityState_Light::set_allocated_name(std::string* value) { _impl_.name_.Set("", GetArena()); } #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:NSPanelEntityState.Light.name) + // @@protoc_insertion_point(field_set_allocated:NSPanelEntityState.Thermostat.name) } -// int32 brightness = 3; -inline void NSPanelEntityState_Light::clear_brightness() { +// float current_temperature = 3; +inline void NSPanelEntityState_Thermostat::clear_current_temperature() { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.brightness_ = 0; + _impl_.current_temperature_ = 0; } -inline ::int32_t NSPanelEntityState_Light::brightness() const { - // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.brightness) - return _internal_brightness(); +inline float NSPanelEntityState_Thermostat::current_temperature() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.current_temperature) + return _internal_current_temperature(); } -inline void NSPanelEntityState_Light::set_brightness(::int32_t value) { - _internal_set_brightness(value); - // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.brightness) +inline void NSPanelEntityState_Thermostat::set_current_temperature(float value) { + _internal_set_current_temperature(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Thermostat.current_temperature) } -inline ::int32_t NSPanelEntityState_Light::_internal_brightness() const { +inline float NSPanelEntityState_Thermostat::_internal_current_temperature() const { ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.brightness_; + return _impl_.current_temperature_; } -inline void NSPanelEntityState_Light::_internal_set_brightness(::int32_t value) { +inline void NSPanelEntityState_Thermostat::_internal_set_current_temperature(float value) { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.brightness_ = value; + _impl_.current_temperature_ = value; } -// int32 color_temp = 4; -inline void NSPanelEntityState_Light::clear_color_temp() { +// bool has_current_temperature = 4; +inline void NSPanelEntityState_Thermostat::clear_has_current_temperature() { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.color_temp_ = 0; + _impl_.has_current_temperature_ = false; } -inline ::int32_t NSPanelEntityState_Light::color_temp() const { - // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.color_temp) - return _internal_color_temp(); +inline bool NSPanelEntityState_Thermostat::has_current_temperature() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.has_current_temperature) + return _internal_has_current_temperature(); } -inline void NSPanelEntityState_Light::set_color_temp(::int32_t value) { - _internal_set_color_temp(value); - // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.color_temp) +inline void NSPanelEntityState_Thermostat::set_has_current_temperature(bool value) { + _internal_set_has_current_temperature(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Thermostat.has_current_temperature) } -inline ::int32_t NSPanelEntityState_Light::_internal_color_temp() const { +inline bool NSPanelEntityState_Thermostat::_internal_has_current_temperature() const { ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.color_temp_; + return _impl_.has_current_temperature_; } -inline void NSPanelEntityState_Light::_internal_set_color_temp(::int32_t value) { +inline void NSPanelEntityState_Thermostat::_internal_set_has_current_temperature(bool value) { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.color_temp_ = value; + _impl_.has_current_temperature_ = value; } -// int32 hue = 5; -inline void NSPanelEntityState_Light::clear_hue() { +// float set_temperature = 5; +inline void NSPanelEntityState_Thermostat::clear_set_temperature() { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.hue_ = 0; + _impl_.set_temperature_ = 0; } -inline ::int32_t NSPanelEntityState_Light::hue() const { - // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.hue) - return _internal_hue(); +inline float NSPanelEntityState_Thermostat::set_temperature() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.set_temperature) + return _internal_set_temperature(); } -inline void NSPanelEntityState_Light::set_hue(::int32_t value) { - _internal_set_hue(value); - // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.hue) +inline void NSPanelEntityState_Thermostat::set_set_temperature(float value) { + _internal_set_set_temperature(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Thermostat.set_temperature) } -inline ::int32_t NSPanelEntityState_Light::_internal_hue() const { +inline float NSPanelEntityState_Thermostat::_internal_set_temperature() const { ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.hue_; + return _impl_.set_temperature_; } -inline void NSPanelEntityState_Light::_internal_set_hue(::int32_t value) { +inline void NSPanelEntityState_Thermostat::_internal_set_set_temperature(float value) { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.hue_ = value; + _impl_.set_temperature_ = value; } -// int32 saturation = 6; -inline void NSPanelEntityState_Light::clear_saturation() { +// float step_size = 6; +inline void NSPanelEntityState_Thermostat::clear_step_size() { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.saturation_ = 0; + _impl_.step_size_ = 0; } -inline ::int32_t NSPanelEntityState_Light::saturation() const { - // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.saturation) - return _internal_saturation(); +inline float NSPanelEntityState_Thermostat::step_size() const { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.step_size) + return _internal_step_size(); } -inline void NSPanelEntityState_Light::set_saturation(::int32_t value) { - _internal_set_saturation(value); - // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.saturation) +inline void NSPanelEntityState_Thermostat::set_step_size(float value) { + _internal_set_step_size(value); + // @@protoc_insertion_point(field_set:NSPanelEntityState.Thermostat.step_size) } -inline ::int32_t NSPanelEntityState_Light::_internal_saturation() const { +inline float NSPanelEntityState_Thermostat::_internal_step_size() const { ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.saturation_; + return _impl_.step_size_; } -inline void NSPanelEntityState_Light::_internal_set_saturation(::int32_t value) { +inline void NSPanelEntityState_Thermostat::_internal_set_step_size(float value) { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.saturation_ = value; + _impl_.step_size_ = value; } -// bool can_color_temp = 7; -inline void NSPanelEntityState_Light::clear_can_color_temp() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.can_color_temp_ = false; -} -inline bool NSPanelEntityState_Light::can_color_temp() const { - // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.can_color_temp) - return _internal_can_color_temp(); -} -inline void NSPanelEntityState_Light::set_can_color_temp(bool value) { - _internal_set_can_color_temp(value); - // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.can_color_temp) -} -inline bool NSPanelEntityState_Light::_internal_can_color_temp() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.can_color_temp_; +// repeated .NSPanelEntityState.Thermostat.ThermostatOption options = 7; +inline int NSPanelEntityState_Thermostat::_internal_options_size() const { + return _internal_options().size(); } -inline void NSPanelEntityState_Light::_internal_set_can_color_temp(bool value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.can_color_temp_ = value; +inline int NSPanelEntityState_Thermostat::options_size() const { + return _internal_options_size(); } - -// bool can_color = 8; -inline void NSPanelEntityState_Light::clear_can_color() { +inline void NSPanelEntityState_Thermostat::clear_options() { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.can_color_ = false; -} -inline bool NSPanelEntityState_Light::can_color() const { - // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.can_color) - return _internal_can_color(); -} -inline void NSPanelEntityState_Light::set_can_color(bool value) { - _internal_set_can_color(value); - // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.can_color) + _impl_.options_.Clear(); } -inline bool NSPanelEntityState_Light::_internal_can_color() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.can_color_; +inline ::NSPanelEntityState_Thermostat_ThermostatOption* NSPanelEntityState_Thermostat::mutable_options(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:NSPanelEntityState.Thermostat.options) + return _internal_mutable_options()->Mutable(index); } -inline void NSPanelEntityState_Light::_internal_set_can_color(bool value) { +inline ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption>* NSPanelEntityState_Thermostat::mutable_options() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable_list:NSPanelEntityState.Thermostat.options) ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.can_color_ = value; + return _internal_mutable_options(); } - -// .NSPanelEntityState.Light.LightMode current_light_mode = 9; -inline void NSPanelEntityState_Light::clear_current_light_mode() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.current_light_mode_ = 0; +inline const ::NSPanelEntityState_Thermostat_ThermostatOption& NSPanelEntityState_Thermostat::options(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelEntityState.Thermostat.options) + return _internal_options().Get(index); } -inline ::NSPanelEntityState_Light_LightMode NSPanelEntityState_Light::current_light_mode() const { - // @@protoc_insertion_point(field_get:NSPanelEntityState.Light.current_light_mode) - return _internal_current_light_mode(); +inline ::NSPanelEntityState_Thermostat_ThermostatOption* NSPanelEntityState_Thermostat::add_options() ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::NSPanelEntityState_Thermostat_ThermostatOption* _add = _internal_mutable_options()->Add(); + // @@protoc_insertion_point(field_add:NSPanelEntityState.Thermostat.options) + return _add; } -inline void NSPanelEntityState_Light::set_current_light_mode(::NSPanelEntityState_Light_LightMode value) { - _internal_set_current_light_mode(value); - // @@protoc_insertion_point(field_set:NSPanelEntityState.Light.current_light_mode) +inline const ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption>& NSPanelEntityState_Thermostat::options() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:NSPanelEntityState.Thermostat.options) + return _internal_options(); } -inline ::NSPanelEntityState_Light_LightMode NSPanelEntityState_Light::_internal_current_light_mode() const { +inline const ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption>& +NSPanelEntityState_Thermostat::_internal_options() const { ::google::protobuf::internal::TSanRead(&_impl_); - return static_cast<::NSPanelEntityState_Light_LightMode>(_impl_.current_light_mode_); + return _impl_.options_; } -inline void NSPanelEntityState_Light::_internal_set_current_light_mode(::NSPanelEntityState_Light_LightMode value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.current_light_mode_ = value; +inline ::google::protobuf::RepeatedPtrField<::NSPanelEntityState_Thermostat_ThermostatOption>* +NSPanelEntityState_Thermostat::_internal_mutable_options() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.options_; } // ------------------------------------------------------------------- @@ -911,6 +2149,85 @@ inline ::NSPanelEntityState_Light* NSPanelEntityState::mutable_light() ABSL_ATTR return _msg; } +// .NSPanelEntityState.Thermostat thermostat = 2; +inline bool NSPanelEntityState::has_thermostat() const { + return entity_case() == kThermostat; +} +inline bool NSPanelEntityState::_internal_has_thermostat() const { + return entity_case() == kThermostat; +} +inline void NSPanelEntityState::set_has_thermostat() { + _impl_._oneof_case_[0] = kThermostat; +} +inline void NSPanelEntityState::clear_thermostat() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (entity_case() == kThermostat) { + if (GetArena() == nullptr) { + delete _impl_.entity_.thermostat_; + } else if (::google::protobuf::internal::DebugHardenClearOneofMessageOnArena()) { + ::google::protobuf::internal::MaybePoisonAfterClear(_impl_.entity_.thermostat_); + } + clear_has_entity(); + } +} +inline ::NSPanelEntityState_Thermostat* NSPanelEntityState::release_thermostat() { + // @@protoc_insertion_point(field_release:NSPanelEntityState.thermostat) + if (entity_case() == kThermostat) { + clear_has_entity(); + auto* temp = _impl_.entity_.thermostat_; + if (GetArena() != nullptr) { + temp = ::google::protobuf::internal::DuplicateIfNonNull(temp); + } + _impl_.entity_.thermostat_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::NSPanelEntityState_Thermostat& NSPanelEntityState::_internal_thermostat() const { + return entity_case() == kThermostat ? *_impl_.entity_.thermostat_ : reinterpret_cast<::NSPanelEntityState_Thermostat&>(::_NSPanelEntityState_Thermostat_default_instance_); +} +inline const ::NSPanelEntityState_Thermostat& NSPanelEntityState::thermostat() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:NSPanelEntityState.thermostat) + return _internal_thermostat(); +} +inline ::NSPanelEntityState_Thermostat* NSPanelEntityState::unsafe_arena_release_thermostat() { + // @@protoc_insertion_point(field_unsafe_arena_release:NSPanelEntityState.thermostat) + if (entity_case() == kThermostat) { + clear_has_entity(); + auto* temp = _impl_.entity_.thermostat_; + _impl_.entity_.thermostat_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void NSPanelEntityState::unsafe_arena_set_allocated_thermostat(::NSPanelEntityState_Thermostat* value) { + // We rely on the oneof clear method to free the earlier contents + // of this oneof. We can directly use the pointer we're given to + // set the new value. + clear_entity(); + if (value) { + set_has_thermostat(); + _impl_.entity_.thermostat_ = value; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:NSPanelEntityState.thermostat) +} +inline ::NSPanelEntityState_Thermostat* NSPanelEntityState::_internal_mutable_thermostat() { + if (entity_case() != kThermostat) { + clear_entity(); + set_has_thermostat(); + _impl_.entity_.thermostat_ = + ::google::protobuf::Message::DefaultConstruct<::NSPanelEntityState_Thermostat>(GetArena()); + } + return _impl_.entity_.thermostat_; +} +inline ::NSPanelEntityState_Thermostat* NSPanelEntityState::mutable_thermostat() ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::NSPanelEntityState_Thermostat* _msg = _internal_mutable_thermostat(); + // @@protoc_insertion_point(field_mutable:NSPanelEntityState.thermostat) + return _msg; +} + inline bool NSPanelEntityState::has_entity() const { return entity_case() != ENTITY_NOT_SET; } diff --git a/docker/MQTTManager/include/room/room.cpp b/docker/MQTTManager/include/room/room.cpp index d1363aa6..ae28f21f 100644 --- a/docker/MQTTManager/include/room/room.cpp +++ b/docker/MQTTManager/include/room/room.cpp @@ -52,8 +52,8 @@ void Room::reload_config() { auto db_room = database_manager::database.get(this->_id); this->_name = db_room.friendly_name; this->_display_order = db_room.display_order; - this->_mqtt_state_topic = fmt::format("nspanel/mqttmanager_{}/room/{}/state", MqttManagerConfig::get_settings().manager_address, this->_id); - this->_mqtt_temperature_state_topic = fmt::format("nspanel/mqttmanager_{}/room/{}/temperature_state", MqttManagerConfig::get_settings().manager_address, this->_id); + this->_mqtt_state_topic = fmt::format("nspanel/mqttmanager_{}/room/{}/state", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS), this->_id); + this->_mqtt_temperature_state_topic = fmt::format("nspanel/mqttmanager_{}/room/{}/temperature_state", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS), this->_id); if (!db_room.room_temp_provider.empty() && !db_room.room_temp_sensor.empty()) { MQTT_MANAGER_ENTITY_CONTROLLER temperature_sensor_controller; @@ -263,6 +263,13 @@ std::string Room::get_temperature_sensor_mqtt_topic() { return ""; } +std::expected Room::get_temperature() { + if (!this->has_temperature_sensor()) { + return std::unexpected("No temperature sensor configured"); + } + return this->_last_room_temperature_value; +} + void Room::page_changed_callback(RoomEntitiesPage *page) { this->_room_changed_callbacks(this); if (this->_send_status_updates) { @@ -273,7 +280,7 @@ void Room::page_changed_callback(RoomEntitiesPage *page) { void Room::command_callback(NSPanelMQTTManagerCommand &command) { if (command.has_first_page_turn_on() && command.first_page_turn_on().selected_room() == this->_id && !command.first_page_turn_on().global()) { SPDLOG_DEBUG("Room {}:{} got command to turn lights on from first page.", this->_id, this->_name); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { SPDLOG_DEBUG("Optimistic mode enabled, will not send state updates via callbacks in room."); this->_send_status_updates = false; } @@ -322,7 +329,7 @@ void Room::command_callback(NSPanelMQTTManagerCommand &command) { lights_list[i]->command_callback(cmd); } - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_send_room_state_update(); this->_send_status_updates = true; SPDLOG_DEBUG("Optimistic mode enabled, reenabled send state updates via callbacks in room."); @@ -437,9 +444,10 @@ void Room::_send_room_state_update() { if (num_kelvin_lights_total > 0) { float average_kelvin = (float)total_kelvin_level_all / num_kelvin_lights_total; - average_kelvin -= MqttManagerConfig::get_settings().color_temp_min; - uint8_t kelvin_pct = (average_kelvin / (MqttManagerConfig::get_settings().color_temp_max - MqttManagerConfig::get_settings().color_temp_min)) * 100; - if (MqttManagerConfig::get_settings().reverse_color_temperature_slider) { + average_kelvin -= MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + average_kelvin = std::max(average_kelvin, 0.0f); + uint8_t kelvin_pct = (average_kelvin / (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX) - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN))) * 100; + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::REVERSE_COLOR_TEMP)) { kelvin_pct = 100 - kelvin_pct; } @@ -458,9 +466,9 @@ void Room::_send_room_state_update() { if (num_kelvin_lights_table > 0) { float average_kelvin = (float)total_kelvin_table / num_kelvin_lights_table; - average_kelvin -= MqttManagerConfig::get_settings().color_temp_min; - uint8_t kelvin_pct = (average_kelvin / (MqttManagerConfig::get_settings().color_temp_max - MqttManagerConfig::get_settings().color_temp_min)) * 100; - if (MqttManagerConfig::get_settings().reverse_color_temperature_slider) { + average_kelvin -= MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint8_t kelvin_pct = (average_kelvin / (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX) - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN))) * 100; + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::REVERSE_COLOR_TEMP)) { kelvin_pct = 100 - kelvin_pct; } @@ -480,9 +488,9 @@ void Room::_send_room_state_update() { if (num_kelvin_lights_ceiling > 0) { float average_kelvin = (float)total_kelvin_ceiling / num_kelvin_lights_ceiling; - average_kelvin -= MqttManagerConfig::get_settings().color_temp_min; - uint8_t kelvin_pct = (average_kelvin / (MqttManagerConfig::get_settings().color_temp_max - MqttManagerConfig::get_settings().color_temp_min)) * 100; - if (MqttManagerConfig::get_settings().reverse_color_temperature_slider) { + average_kelvin -= MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint8_t kelvin_pct = (average_kelvin / (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX) - MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN))) * 100; + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::REVERSE_COLOR_TEMP)) { kelvin_pct = 100 - kelvin_pct; } @@ -536,6 +544,7 @@ void Room::_room_temperature_state_change_callback(nlohmann::json data) { try { float temperature = std::stof(data["event"]["data"]["new_state"]["state"].get()); + this->_last_room_temperature_value = temperature; MQTT_Manager::publish(this->_mqtt_temperature_state_topic, fmt::format("{:.1f}", temperature), true); } catch (const std::invalid_argument &e) { SPDLOG_WARN("Failed to convert temperature to float. Will send raw string to panel.: {}", e.what()); diff --git a/docker/MQTTManager/include/room/room.hpp b/docker/MQTTManager/include/room/room.hpp index 3465ca5b..d03c6719 100644 --- a/docker/MQTTManager/include/room/room.hpp +++ b/docker/MQTTManager/include/room/room.hpp @@ -5,6 +5,7 @@ #include "protobuf_nspanel.pb.h" #include "room/room_entities_page.hpp" #include +#include #include #include #include @@ -96,6 +97,11 @@ class Room { */ std::string get_temperature_sensor_mqtt_topic(); + /* + * Get the temperature of this room if it has a temperature sensor configured and loaded properly. + */ + std::expected get_temperature(); + /** * Callback when that gets run when an entitiy has changed state */ @@ -173,6 +179,9 @@ class Room { // Room temperature sensor item name/home assistant entity id std::string _room_temp_sensor; + + // Last value received via the room temperature sensor + float _last_room_temperature_value; }; #endif // !MQTT_MANAGER_ROOM_H diff --git a/docker/MQTTManager/include/room/room_entities_page.cpp b/docker/MQTTManager/include/room/room_entities_page.cpp index 17b3eefe..eb0f0f79 100644 --- a/docker/MQTTManager/include/room/room_entities_page.cpp +++ b/docker/MQTTManager/include/room/room_entities_page.cpp @@ -7,6 +7,7 @@ #include "protobuf_nspanel.pb.h" #include "scenes/scene.hpp" #include "switch/switch.hpp" +#include "thermostat/thermostat.hpp" #include #include #include @@ -56,7 +57,7 @@ void RoomEntitiesPage::reload_config(bool send_state_update) { std::lock_guard mutex_guard(this->_page_settings_mutex); this->_page_settings = db_room; - this->_mqtt_state_topic = fmt::format("nspanel/mqttmanager_{}/entity_pages/{}/state", MqttManagerConfig::get_settings().manager_address, this->_id); + this->_mqtt_state_topic = fmt::format("nspanel/mqttmanager_{}/entity_pages/{}/state", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS), this->_id); } SPDLOG_DEBUG("RoomEntitiesPage {} loaded successfully.", this->_id); @@ -171,7 +172,6 @@ void RoomEntitiesPage::_send_mqtt_state_update() { NSPanelRoomEntitiesPage_EntitySlot *entity_slot = proto_state.add_entities(); entity_slot->set_name(light->get_name()); entity_slot->set_room_view_position(i); - // TODO: Move state icons for panel from GUI_DATA to manager and convert this! entity_slot->set_icon(light->get_icon()); entity_slot->set_pco(light->get_icon_color()); entity_slot->set_pco2(light->get_icon_active_color()); @@ -182,7 +182,6 @@ void RoomEntitiesPage::_send_mqtt_state_update() { NSPanelRoomEntitiesPage_EntitySlot *entity_slot = proto_state.add_entities(); entity_slot->set_name(switch_entity->get_name()); entity_slot->set_room_view_position(i); - // TODO: Move state icons for panel from GUI_DATA to manager and convert this! entity_slot->set_icon(switch_entity->get_icon()); entity_slot->set_pco(switch_entity->get_icon_color()); entity_slot->set_pco2(switch_entity->get_icon_active_color()); @@ -192,7 +191,6 @@ void RoomEntitiesPage::_send_mqtt_state_update() { NSPanelRoomEntitiesPage_EntitySlot *entity_slot = proto_state.add_entities(); entity_slot->set_name(button_entity->get_name()); entity_slot->set_room_view_position(i); - // TODO: Move state icons for panel from GUI_DATA to manager and convert this! entity_slot->set_icon(button_entity->get_icon()); entity_slot->set_pco(button_entity->get_icon_color()); entity_slot->set_pco2(button_entity->get_icon_active_color()); @@ -202,7 +200,6 @@ void RoomEntitiesPage::_send_mqtt_state_update() { NSPanelRoomEntitiesPage_EntitySlot *entity_slot = proto_state.add_entities(); entity_slot->set_name(scene->get_name()); entity_slot->set_room_view_position(i); - // TODO: Move state icons for panel from GUI_DATA to manager and convert this! entity_slot->set_icon(scene->get_icon()); entity_slot->set_pco(scene->get_icon_color()); entity_slot->set_pco2(scene->get_icon_active_color()); @@ -212,6 +209,18 @@ void RoomEntitiesPage::_send_mqtt_state_update() { } else { entity_slot->set_can_save_scene(false); } + } else if (entity->get_type() == MQTT_MANAGER_ENTITY_TYPE::THERMOSTAT) { + std::shared_ptr thermostat = std::static_pointer_cast(entity); + NSPanelRoomEntitiesPage_EntitySlot *entity_slot = proto_state.add_entities(); + entity_slot->set_name(thermostat->get_name()); + entity_slot->set_room_view_position(i); + entity_slot->set_icon(thermostat->get_icon()); + entity_slot->set_pco(thermostat->get_icon_color()); + entity_slot->set_pco2(thermostat->get_icon_active_color()); + entity_slot->set_can_save_scene(false); // Entity is not a scene. + entity_slot->set_mqtt_state_topic(thermostat->get_mqtt_state_topic()); + + SPDLOG_DEBUG("Thermostat icon: {} (0x{:X})", entity_slot->icon(), entity_slot->icon()[0]); } else { SPDLOG_ERROR("Unknown entity type {} while processing EntityWrapper while building NSPanelRoomEntitiesPage protobuf object.", (int)entity->get_type()); } diff --git a/docker/MQTTManager/include/scenes/home_assistant_scene.cpp b/docker/MQTTManager/include/scenes/home_assistant_scene.cpp index 03d88bc6..a6832ef0 100644 --- a/docker/MQTTManager/include/scenes/home_assistant_scene.cpp +++ b/docker/MQTTManager/include/scenes/home_assistant_scene.cpp @@ -1,6 +1,7 @@ #include "database_manager/database_manager.hpp" #include "entity/entity.hpp" #include "entity_manager/entity_manager.hpp" +#include "room/room.hpp" #include #include #include @@ -37,12 +38,33 @@ void HomeAssistantScene::reload_config() { void HomeAssistantScene::activate() { SPDLOG_INFO("Activating scene {}::{}.", this->_id, this->_name); - nlohmann::json service_data; - service_data["type"] = "call_service"; - service_data["domain"] = "scene"; - service_data["service"] = "turn_on"; - service_data["target"]["entity_id"] = this->_entity_id; - HomeAssistantManager::send_json(service_data); + if (this->_entity_id.starts_with("scene.")) { + nlohmann::json service_data; + service_data["type"] = "call_service"; + service_data["domain"] = "scene"; + service_data["service"] = "turn_on"; + service_data["target"]["entity_id"] = this->_entity_id; + HomeAssistantManager::send_json(service_data); + } else if (this->_entity_id.starts_with("script.")) { + nlohmann::json service_data; + auto room = EntityManager::get_room(this->_room_id); + if (!this->_is_global && room.has_value()) { + SPDLOG_DEBUG("Sending script call with context."); + nlohmann::json context; + context["room_name"] = (*room)->get_name(); + context["room_id"] = this->_room_id; + service_data["service_data"]["variables"]["nspanelmanager"] = context; + SPDLOG_DEBUG("Sending script with context: "); + SPDLOG_DEBUG(context.dump()); + } + service_data["type"] = "call_service"; + service_data["domain"] = "script"; + service_data["service"] = "turn_on"; + service_data["target"]["entity_id"] = this->_entity_id; + HomeAssistantManager::send_json(service_data); + } else { + SPDLOG_ERROR("Got request to turn on home assistant scene {}::{} but the entity '{}' is not of known type scene or script.", this->_id, this->_name, this->_entity_id); + } } void HomeAssistantScene::save() { @@ -65,15 +87,6 @@ MQTT_MANAGER_ENTITY_CONTROLLER HomeAssistantScene::get_controller() { } void HomeAssistantScene::post_init() { - if (!this->_is_global) { - auto room_entity = EntityManager::get_room(this->_room_id); - if (room_entity) { - this->_room = *room_entity; - } else { - SPDLOG_ERROR("Did not find any room with room ID: {}. Will not continue loading.", this->_room_id); - return; - } - } } std::string HomeAssistantScene::get_name() { diff --git a/docker/MQTTManager/include/scenes/home_assistant_scene.hpp b/docker/MQTTManager/include/scenes/home_assistant_scene.hpp index 8585ba1a..e2acc260 100644 --- a/docker/MQTTManager/include/scenes/home_assistant_scene.hpp +++ b/docker/MQTTManager/include/scenes/home_assistant_scene.hpp @@ -25,7 +25,6 @@ class HomeAssistantScene : public Scene { std::string _name; std::string _entity_id; uint16_t _room_id; - std::shared_ptr _room; }; #endif // !HOME_ASSISTANT_SCENE diff --git a/docker/MQTTManager/include/scenes/openhab_scene.cpp b/docker/MQTTManager/include/scenes/openhab_scene.cpp index 148620ee..a039d8c6 100644 --- a/docker/MQTTManager/include/scenes/openhab_scene.cpp +++ b/docker/MQTTManager/include/scenes/openhab_scene.cpp @@ -39,9 +39,10 @@ void OpenhabScene::reload_config() { void OpenhabScene::activate() { SPDLOG_INFO("Activating scene {}::{}.", this->_id, this->_name); - std::string openhab_trigger_scene_url = fmt::format("{}/rest/rules/{}/runnow", MqttManagerConfig::get_setting_with_default("openhab_address", ""), this->_entity_id); + std::string openhab_trigger_scene_url = fmt::format("{}/rest/rules/{}/runnow", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPENHAB_ADDRESS), this->_entity_id); + std::string openhab_token = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPENHAB_TOKEN); std::list headers = { - fmt::format("Authorization: Bearer {}", MqttManagerConfig::get_setting_with_default("openhab_token", "")).c_str(), + fmt::format("Authorization: Bearer {}", openhab_token).c_str(), "Content-type: application/json"}; std::string response_data; std::string post_data = ""; diff --git a/docker/MQTTManager/include/switch/home_assistant_switch.cpp b/docker/MQTTManager/include/switch/home_assistant_switch.cpp index d2aaa490..48bc3fd1 100644 --- a/docker/MQTTManager/include/switch/home_assistant_switch.cpp +++ b/docker/MQTTManager/include/switch/home_assistant_switch.cpp @@ -53,12 +53,12 @@ void HomeAssistantSwitch::send_state_update_to_controller() { service_data["domain"] = "switch"; if (this->_requested_state) { service_data["service"] = "turn_on"; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = true; } } else { service_data["service"] = "turn_off"; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = false; } } @@ -67,12 +67,12 @@ void HomeAssistantSwitch::send_state_update_to_controller() { service_data["domain"] = "input_boolean"; if (this->_requested_state) { service_data["service"] = "turn_on"; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = true; } } else { service_data["service"] = "turn_off"; - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = false; } } @@ -81,7 +81,7 @@ void HomeAssistantSwitch::send_state_update_to_controller() { SPDLOG_ERROR("Could not determin domain for switch entity {}::{}. HA Name: {}", this->_id, this->_name, this->_home_assistant_name); } - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_entity_changed_callbacks(this); } } diff --git a/docker/MQTTManager/include/switch/openhab_switch.cpp b/docker/MQTTManager/include/switch/openhab_switch.cpp index c6008bb4..4951aa59 100644 --- a/docker/MQTTManager/include/switch/openhab_switch.cpp +++ b/docker/MQTTManager/include/switch/openhab_switch.cpp @@ -72,7 +72,7 @@ void OpenhabSwitch::send_state_update_to_controller() { payload_data["value"] = this->_requested_state ? "ON" : "OFF"; service_data["payload"] = payload_data.dump(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = this->_requested_state; this->_entity_changed_callbacks(this); } @@ -92,6 +92,9 @@ void OpenhabSwitch::openhab_event_callback(nlohmann::json data) { return; } + SPDLOG_DEBUG("Switch received callback payload:"); + SPDLOG_DEBUG(data.dump(2)); + std::string topic_item = topic_parts[2]; nlohmann::json payload = nlohmann::json::parse(std::string(data["payload"])); if (topic_item.compare(this->_openhab_on_off_item) == 0) { @@ -113,8 +116,7 @@ void OpenhabSwitch::openhab_event_callback(nlohmann::json data) { SPDLOG_TRACE("OpenHAB light {}::{} Got initial data from OpenHAB via custom ItemStateFetched event.", this->_id, this->_name); if (this->_openhab_on_off_item.compare(data["payload"]["name"]) == 0) { if (data["payload"]["state"].is_string()) { - nlohmann::json payload = nlohmann::json::parse(std::string(data["payload"])); - bool state = std::string(payload["value"]).compare("ON") == 0; + bool state = std::string(data["payload"]["state"]).compare("ON") == 0; SPDLOG_DEBUG("Switch {}::{} got new state {}, current state: {}.", this->_id, this->_name, state, this->_current_state); if (state != this->_current_state) { this->_current_state = state; diff --git a/docker/MQTTManager/include/switch/switch.cpp b/docker/MQTTManager/include/switch/switch.cpp index a4846904..e6f13dab 100644 --- a/docker/MQTTManager/include/switch/switch.cpp +++ b/docker/MQTTManager/include/switch/switch.cpp @@ -88,7 +88,7 @@ void SwitchEntity::turn_on(bool send_update) { if (send_update) { this->send_state_update_to_controller(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = true; } } @@ -100,7 +100,7 @@ void SwitchEntity::turn_off(bool send_update) { if (send_update) { this->send_state_update_to_controller(); - if (MqttManagerConfig::get_settings().optimistic_mode) { + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { this->_current_state = false; } } diff --git a/docker/MQTTManager/include/thermostat/home_assistant_thermostat.cpp b/docker/MQTTManager/include/thermostat/home_assistant_thermostat.cpp new file mode 100644 index 00000000..53df1baf --- /dev/null +++ b/docker/MQTTManager/include/thermostat/home_assistant_thermostat.cpp @@ -0,0 +1,671 @@ +#include "home_assistant_thermostat.hpp" +#include "database_manager/database_manager.hpp" +#include "entity/entity.hpp" +#include "mqtt_manager_config/mqtt_manager_config.hpp" +#include "protobuf_nspanel.pb.h" +#include "thermostat/thermostat.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +HomeAssistantThermostat::HomeAssistantThermostat(uint32_t thermostat_id) : ThermostatEntity(thermostat_id) { + // Process Home Assistant specific details. General thermostat data is loaded in the "ThermostatEntity" constructor. + if (this->_controller != MQTT_MANAGER_ENTITY_CONTROLLER::HOME_ASSISTANT) { + SPDLOG_ERROR("HomeAssistantThermostat has not been recognized as controlled by HOME_ASSISTANT. Will stop processing thermostat."); + return; + } + + nlohmann::json entity_data; + try { + auto thermostat = database_manager::database.get(this->_id); + entity_data = thermostat.get_entity_data_json(); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to load thermostat {}: {}", this->_id, e.what()); + return; + } + + if (entity_data.contains("home_assistant_name")) { + this->_home_assistant_name = entity_data["home_assistant_name"]; + } else { + SPDLOG_ERROR("No home assistant name defined for Thermostat {}::{}", this->_id, this->_name); + return; + } + SPDLOG_DEBUG("Loaded thermostat {}::{}, home assistant entity ID: {}", this->_id, this->_name, this->_home_assistant_name); + HomeAssistantManager::attach_event_observer(this->_home_assistant_name, boost::bind(&HomeAssistantThermostat::home_assistant_event_callback, this, _1)); + + if (this->_home_assistant_name.rfind("climate.", 0) != 0) { + SPDLOG_ERROR("Unknown type of home assistant entity '{}'. Expected climate.", this->_home_assistant_name); + } + + this->send_state_update_to_nspanel(); // Send initial state to NSPanel +} + +HomeAssistantThermostat::~HomeAssistantThermostat() { + HomeAssistantManager::detach_event_observer(this->_home_assistant_name, boost::bind(&HomeAssistantThermostat::home_assistant_event_callback, this, _1)); +} + +void HomeAssistantThermostat::reload_config() { + ThermostatEntity::reload_config(); + HomeAssistantManager::detach_event_observer(this->_home_assistant_name, boost::bind(&HomeAssistantThermostat::home_assistant_event_callback, this, _1)); + + nlohmann::json entity_data; + try { + auto thermostat = database_manager::database.get(this->_id); + entity_data = thermostat.get_entity_data_json(); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to load thermostat {}: {}", this->_id, e.what()); + return; + } + + if (entity_data.contains("home_assistant_name")) { + this->_home_assistant_name = entity_data["home_assistant_name"]; + } else { + SPDLOG_ERROR("No home assistant name defined for Thermostat {}::{}", this->_id, this->_name); + return; + } + + // Reattach event observer in case entity has changed. + HomeAssistantManager::attach_event_observer(this->_home_assistant_name, boost::bind(&HomeAssistantThermostat::home_assistant_event_callback, this, _1)); +} + +void HomeAssistantThermostat::send_state_update_to_controller() { + if (this->_requested_mode != this->_current_mode) { + nlohmann::json command = { + {"type", "call_service"}, + {"domain", "climate"}, + {"target", {{"entity_id", this->_home_assistant_name}}}, + {"service", "set_hvac_mode"}, + {"service_data", {{"hvac_mode", this->_requested_mode.value}}}}; + HomeAssistantManager::send_json(command); + } + + if (this->_requested_fan_mode != this->_current_fan_mode) { + nlohmann::json command = { + {"type", "call_service"}, + {"domain", "climate"}, + {"target", {{"entity_id", this->_home_assistant_name}}}, + {"service", "set_fan_mode"}, + {"service_data", {{"fan_mode", this->_requested_fan_mode.value}}}}; + HomeAssistantManager::send_json(command); + } + + if (this->_requested_preset != this->_current_preset) { + nlohmann::json command = { + {"type", "call_service"}, + {"domain", "climate"}, + {"target", {{"entity_id", this->_home_assistant_name}}}, + {"service", "set_preset_mode"}, + {"service_data", {{"preset_mode", this->_requested_preset.value}}}}; + HomeAssistantManager::send_json(command); + } + + if (this->_requested_swing_mode != this->_current_swing_mode) { + nlohmann::json command = { + {"type", "call_service"}, + {"domain", "climate"}, + {"target", {{"entity_id", this->_home_assistant_name}}}, + {"service", "set_swing_mode"}, + {"service_data", {{"swing_mode", this->_requested_swing_mode.value}}}}; + HomeAssistantManager::send_json(command); + } + + if (this->_requested_swingh_mode != this->_current_swingh_mode) { + nlohmann::json command = { + {"type", "call_service"}, + {"domain", "climate"}, + {"target", {{"entity_id", this->_home_assistant_name}}}, + {"service", "set_swing_horizontal_mode"}, + {"service_data", {{"swing_horizontal_mode", this->_requested_swingh_mode.value}}}}; + HomeAssistantManager::send_json(command); + } + + if (this->_requested_temperature != this->_current_temperature) { + nlohmann::json command = { + {"type", "call_service"}, + {"domain", "climate"}, + {"target", {{"entity_id", this->_home_assistant_name}}}, + {"service", "set_temperature"}, + {"service_data", {{"temperature", this->_requested_temperature}}}}; + HomeAssistantManager::send_json(command); + } +} + +void HomeAssistantThermostat::home_assistant_event_callback(nlohmann::json data) { + if (!data.contains("event")) [[unlikely]] { + SPDLOG_ERROR("Thermostat {}::{} received malformed event data from HA. Data contains no 'event' key.", this->_id, this->_name); + return; + } else if (!data["event"].contains("event_type")) [[unlikely]] { + SPDLOG_ERROR("Thermostat {}::{} received malformed event data from HA. Data contains no 'event.event_type' key.", this->_id, this->_name); + return; + } else if (!data["event"].contains("data")) [[unlikely]] { + SPDLOG_ERROR("Thermostat {}::{} received malformed event data from HA. Data contains no 'event.data' key.", this->_id, this->_name); + return; + } else if (!data["event"]["data"].contains("entity_id")) [[unlikely]] { + SPDLOG_ERROR("Thermostat {}::{} received malformed event data from HA. Data contains no 'event.data.entity_id' key.", this->_id, this->_name); + return; + } + + if (std::string(data["event"]["event_type"]).compare("state_changed") == 0) { + if (std::string(data["event"]["data"]["entity_id"]).compare(this->_home_assistant_name) == 0) { + SPDLOG_DEBUG("Got event update for HA thermostat {}::{}.", this->_id, this->_name); + nlohmann::json new_state_data = data["event"]["data"]["new_state"]; + nlohmann::json new_state_attributes = new_state_data["attributes"]; + bool changed_attribute = false; + + try { + if (new_state_data.contains("state") && !new_state_data["state"].is_null()) { + std::string new_state = new_state_data["state"].get(); + if (new_state.compare(this->_current_mode.value) != 0) { + auto new_mode = std::find_if(this->_supported_modes.begin(), this->_supported_modes.end(), [&](const ThermostatOptionHolder &mode) { + return mode.value.compare(new_state) == 0; + }); + if (new_mode != this->_supported_modes.end()) { + this->_current_mode = *new_mode; + this->_requested_mode = *new_mode; + changed_attribute = true; + SPDLOG_DEBUG("Thermostat {}::{} got new state: {}", this->_id, this->_name, new_mode->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new state '{}' from HA but that HVAC mode is not supported.", this->_id, this->_name, new_state_data.at("state").get()); + } + } + } + + if (new_state_attributes.contains("fan_mode") && !new_state_attributes["fan_mode"].is_null()) { + std::string new_fan_mode = new_state_attributes.at("fan_mode").get(); + if (new_fan_mode.compare(this->_current_fan_mode.value) != 0) { + auto new_mode = std::find_if(this->_supported_fan_modes.begin(), this->_supported_fan_modes.end(), [&](const ThermostatOptionHolder &mode) { + return mode.value.compare(new_fan_mode) == 0; + }); + if (new_mode != this->_supported_fan_modes.end()) { + this->_current_fan_mode = *new_mode; + this->_requested_fan_mode = *new_mode; + changed_attribute = true; + SPDLOG_DEBUG("Thermostat {}::{} got new fan mode: {}", this->_id, this->_name, new_mode->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new fan mode '{}' from HA but that fan mode is not supported.", this->_id, this->_name, new_fan_mode); + } + } + } + + if (new_state_attributes.contains("preset_mode") && !new_state_attributes["preset_mode"].is_null()) { + std::string new_preset_mode = new_state_attributes.at("preset_mode").get(); + if (new_preset_mode.compare(this->_current_preset.value) != 0) { + auto request_preset = std::find_if(this->_supported_presets.begin(), this->_supported_presets.end(), [&](const ThermostatOptionHolder &mode) { + return mode.value.compare(new_preset_mode) == 0; + }); + if (request_preset != this->_supported_presets.end()) { + this->_current_preset = *request_preset; + this->_requested_preset = *request_preset; + changed_attribute = true; + SPDLOG_DEBUG("Thermostat {}::{} got new preset mode: {}", this->_id, this->_name, request_preset->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new preset mode '{}' from HA but that preset mode is not supported.", this->_id, this->_name, new_preset_mode); + } + } + } + + if (new_state_attributes.contains("swing_mode") && !new_state_attributes["swing_mode"].is_null()) { + std::string new_swing_mode = new_state_attributes.at("swing_mode").get(); + if (new_swing_mode.compare(this->_current_swing_mode.value) != 0) { + auto request_swing_mode = std::find_if(this->_supported_swing_modes.begin(), this->_supported_swing_modes.end(), [&](const ThermostatOptionHolder &mode) { + return mode.value.compare(new_swing_mode) == 0; + }); + if (request_swing_mode != this->_supported_swing_modes.end()) { + this->_current_swing_mode = *request_swing_mode; + this->_requested_swing_mode = *request_swing_mode; + changed_attribute = true; + SPDLOG_DEBUG("Thermostat {}::{} got new swing mode: {}", this->_id, this->_name, request_swing_mode->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new swing mode '{}' from HA but that swing mode is not supported.", this->_id, this->_name, new_swing_mode); + } + } + } + + if (new_state_attributes.contains("swing_horizontal_mode") && !new_state_attributes["swing_horizontal_mode"].is_null()) { + std::string new_swingh_mode = new_state_attributes.at("swing_horizontal_mode").get(); + if (new_swingh_mode.compare(this->_current_swingh_mode.value) != 0) { + auto request_swingh_mode = std::find_if(this->_supported_swingh_modes.begin(), this->_supported_swingh_modes.end(), [&](const ThermostatOptionHolder &mode) { + return mode.value.compare(new_swingh_mode) == 0; + }); + if (request_swingh_mode != this->_supported_swingh_modes.end()) { + this->_current_swingh_mode = *request_swingh_mode; + this->_requested_swingh_mode = *request_swingh_mode; + changed_attribute = true; + SPDLOG_DEBUG("Thermostat {}::{} got new swing mode: {}", this->_id, this->_name, new_swingh_mode); + } else { + SPDLOG_WARN("Thermostat {}::{} got new swing mode '{}' from HA but that swing mode is not supported.", this->_id, this->_name, new_swingh_mode); + } + } + } + + if (new_state_attributes.contains("temperature") && !new_state_attributes["temperature"].is_null()) { + float temperature = new_state_attributes.at("temperature").get(); + if (temperature != this->_current_temperature) { + this->_current_temperature = temperature; + changed_attribute = true; + SPDLOG_DEBUG("Thermostat {}::{} got new temperature: {}", this->_id, this->_name, temperature); + } + } else { + SPDLOG_WARN("Received state update for {}::{} but update has no valid set temperature.", this->_id, this->_name); + } + + } catch (std::exception &e) { + SPDLOG_ERROR("Caught exception when trying to update state for light {}::{} message: {}. Working data: {}", this->_id, this->_name, boost::diagnostic_information(e, true), new_state_attributes.dump()); + } + + if (changed_attribute) { + this->reset_requests(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + } + } + } +} + +// TESTING +#if defined(TEST_MODE) && TEST_MODE == 1 +#include + +class HomeAssistantLightTest : public ::testing::Test { +protected: + HomeAssistantLightTest() { + // Initialize any necessary resources or setup for the tests + } + + void SetUp() override { + + // Initialize any necessary resources or setup for the tests + database_manager::Entity light_entity; + light_entity.entity_type = "light"; + light_entity.friendly_name = "Unit test HA light (light)"; + light_entity.room_id = 1; + light_entity.room_view_position = 2; + light_entity.set_entity_data_json({{"can_color_temperature", true}, + {"can_dim", true}, + {"can_rgb", true}, + {"controlled_by_nspanel_main_page", true}, + {"controller", "home_assistant"}, + {"home_assistant_name", "light.office_ceiling_light"}, + {"is_ceiling_light", true}, + {"openhab_control_mode", "dimmer"}, + {"openhab_item_color_temp", ""}, + {"openhab_item_dimmer", ""}, + {"openhab_item_rgb", ""}, + {"openhab_item_switch", ""}, + {"openhab_name", ""}}); + ha_light_id = database_manager::database.insert(light_entity); + // light_entity = database_manager::database.get(ha_light_id); + + SPDLOG_INFO("HA Light created in DB. Creating instance of light object."); + light = new HomeAssistantLight(ha_light_id); + } + + void TearDown() override { + // Clean up any resources or teardown after the tests + + database_manager::database.remove(ha_light_id); + // database_manager::database.remove(ha_light_switch_id); + } + + HomeAssistantLight *light = nullptr; + + int32_t ha_light_id; + int32_t ha_light_switch_id; +}; + +TEST_F(HomeAssistantLightTest, is_off_by_default) { + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); +} + +TEST_F(HomeAssistantLightTest, is_not_on_after_turn_on_in_nonoptimistic_mode) { + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "false"); + light->turn_on(false); + light->set_brightness(50, true); + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); +} + +TEST_F(HomeAssistantLightTest, is_on_after_turn_on_in_optimistic_mode) { + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + light->turn_on(false); + light->set_brightness(50, true); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); +} + +TEST_F(HomeAssistantLightTest, light_reacts_to_state_changes_from_home_assistant) { + // Enable optimistic mode for this test + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + + nlohmann::json event_data = nlohmann::json::parse(R"( + { + + "event":{ + "context":{ + "id":"01K2WNR8WT8VS6B5HTQ7ZEKSAP", + "parent_id":null, + "user_id":"d28bd745ad714c108c27c31a90406189" + }, + "data":{ + "entity_id":"light.office_ceiling_light", + "new_state":{ + "attributes":{ + "brightness":204, + "color_mode":"color_temp", + "color_temp":443, + "color_temp_kelvin":2257, + "effect":null, + "effect_list":[ + "blink", + "breathe", + "okay", + "channel_change", + "finish_effect", + "stop_effect" + ], + "friendly_name":"office_ceiling_light", + "hs_color":[ + 20, + 80 + ], + "max_color_temp_kelvin":4000, + "max_mireds":454, + "min_color_temp_kelvin":2202, + "min_mireds":250, + "rgb_color":[ + 255, + 149, + 46 + ], + "supported_color_modes":[ + "color_temp" + ], + "supported_features":44, + "xy_color":[ + 0.572, + 0.389 + ] + }, + "context":{ + "id":"01K2WNR8WT8VS6B5HTQ7ZEKSAP", + "parent_id":null, + "user_id":"d28bd745ad714c108c27c31a90406189" + }, + "entity_id":"light.office_ceiling_light", + "last_changed":"2025-08-17T18:45:35.203504+00:00", + "last_reported":"2025-08-17T18:48:00.253850+00:00", + "last_updated":"2025-08-17T18:48:00.253850+00:00", + "state":"on" + }, + "old_state": "removed_as_it_is_not_used" + }, + "event_type":"state_changed", + "origin":"LOCAL", + "time_fired":"2025-08-17T18:48:00.253850+00:00" + }, + "id":3, + "type":"event" + + } )"); + + light->home_assistant_event_callback(event_data); + + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 80); // We sent brightness 204, ie. 80% + EXPECT_EQ(light->get_color_temperature(), 2257); + EXPECT_EQ(light->get_color_temperature(), 2257); + + event_data["event"]["data"]["new_state"]["attributes"]["brightness"] = 153; + event_data["event"]["data"]["new_state"]["attributes"]["color_temp_kelvin"] = 5000; + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); // We sent brightness 153, ie. 60% + EXPECT_EQ(light->get_color_temperature(), 5000); + + event_data["event"]["data"]["new_state"]["state"] = "off"; + event_data["event"]["data"]["new_state"]["attributes"]["brightness"] = 0; + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 60); // Remember last brightness value + + // Change to RGB mode + event_data["event"]["data"]["new_state"]["state"] = "on"; + event_data["event"]["data"]["new_state"]["attributes"]["brightness"] = 153; // 60% + event_data["event"]["data"]["new_state"]["attributes"]["color_mode"] = "xy"; // TODO: Update with correct color mode as sent from HA. + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); + EXPECT_EQ(light->get_hue(), 20); + EXPECT_EQ(light->get_saturation(), 80); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that changes to RGB/HSB values as correctly interpreted + event_data["event"]["data"]["new_state"]["attributes"]["hs_color"][0] = 30; + event_data["event"]["data"]["new_state"]["attributes"]["hs_color"][1] = 60; + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); + EXPECT_EQ(light->get_hue(), 30); + EXPECT_EQ(light->get_saturation(), 60); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that we can revert back to color temp mode + event_data["event"]["data"]["new_state"]["state"] = "on"; + event_data["event"]["data"]["new_state"]["attributes"]["brightness"] = 153; // 60% + event_data["event"]["data"]["new_state"]["attributes"]["color_mode"] = "color_temp"; + event_data["event"]["data"]["new_state"]["attributes"]["color_temp_kelvin"] = 4000; + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); + EXPECT_EQ(light->get_color_temperature(), 4000); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); +} + +#define COLOR_TEMP_PERCENT_TO_KELVIN(min, max, kelvin_pct) (((max - min) / 100) * kelvin_pct + min) +TEST_F(HomeAssistantLightTest, verify_nspanel_command_compliance) { + // Enable optimistic mode for this test + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + // spdlog::set_level(spdlog::level::trace); + + NSPanelMQTTManagerCommand cmd; + cmd.set_nspanel_id(0); + NSPanelMQTTManagerCommand_LightCommand *light_cmd = cmd.mutable_light_command(); + std::vector light_ids = {light->get_id()}; + light_cmd->add_light_ids(light->get_id()); + light_cmd->set_brightness(50); + light_cmd->set_has_brightness(true); + light_cmd->set_color_temperature(50); + light_cmd->set_has_color_temperature(true); + light_cmd->set_has_hue(false); + light_cmd->set_has_saturation(false); + + light->command_callback(cmd); + + // Check that light turns on, sets correct brightness and color temperature. + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 50)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + // Verify that only color temperature can be change. + light_cmd->set_has_brightness(false); + light_cmd->set_color_temperature(70); + light_cmd->set_has_color_temperature(true); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + // Verify that only brightness can be change. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(true); + light_cmd->set_brightness(70); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + // Verify that hue and saturation can be changed and the lights goes into RGB mode. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(false); + light_cmd->set_hue(30); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(60); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 30); + EXPECT_EQ(light->get_saturation(), 60); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that only hue can be changed. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(false); + light_cmd->set_hue(40); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(60); + light_cmd->set_has_saturation(false); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 40); + EXPECT_EQ(light->get_saturation(), 60); // Verify that value is unchanged. + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that only saturation can be changed. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(false); + light_cmd->set_hue(40); + light_cmd->set_has_hue(false); + light_cmd->set_saturation(70); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 40); // Verify that value is unchanged. + EXPECT_EQ(light->get_saturation(), 70); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that brightness can be changed and RGB is kept. + light_cmd->set_has_color_temperature(false); + light_cmd->set_brightness(100); + light_cmd->set_has_brightness(true); + light_cmd->set_hue(40); + light_cmd->set_has_hue(false); + light_cmd->set_saturation(70); + light_cmd->set_has_saturation(false); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 100); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 40); // Verify that value is unchanged. + EXPECT_EQ(light->get_saturation(), 70); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); +} + +TEST_F(HomeAssistantLightTest, verify_optimistic_mode_compliance) { + // Enable optimistic mode for this test + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + // spdlog::set_level(spdlog::level::trace); + + NSPanelMQTTManagerCommand cmd; + cmd.set_nspanel_id(0); + NSPanelMQTTManagerCommand_LightCommand *light_cmd = cmd.mutable_light_command(); + std::vector light_ids = {light->get_id()}; + light_cmd->add_light_ids(light->get_id()); + light_cmd->set_brightness(50); + light_cmd->set_has_brightness(true); + light_cmd->set_color_temperature(50); + light_cmd->set_has_color_temperature(true); + light_cmd->set_has_hue(false); + light_cmd->set_has_saturation(false); + + light->command_callback(cmd); + + // Check that light turns on, sets correct brightness and color temperature. + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 50)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + light_cmd->set_has_brightness(false); + light_cmd->set_has_color_temperature(false); + light_cmd->set_hue(30); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(50); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + + EXPECT_EQ(light->get_state(), true); // Verify light is still on + EXPECT_EQ(light->get_brightness(), 50); // Verify light is still set to 50% brightness + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 50)); // Verify color temp has not changed. + EXPECT_EQ(light->get_hue(), 30); + EXPECT_EQ(light->get_saturation(), 50); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); // Light should have switched to RGB mode. +} + +TEST_F(HomeAssistantLightTest, verify_non_optimistic_mode_compliance) { + // Turn off optimistic mode and verify that no values change. + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "false"); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + // spdlog::set_level(spdlog::level::trace); + + NSPanelMQTTManagerCommand cmd; + cmd.set_nspanel_id(0); + NSPanelMQTTManagerCommand_LightCommand *light_cmd = cmd.mutable_light_command(); + std::vector light_ids = {light->get_id()}; + light_cmd->add_light_ids(light->get_id()); + light_cmd->set_brightness(50); + light_cmd->set_has_brightness(true); + light_cmd->set_color_temperature(50); + light_cmd->set_has_color_temperature(true); + light_cmd->set_has_hue(false); + light_cmd->set_has_saturation(false); + light->command_callback(cmd); + + // Verify light is still off, brightness is still 0 and hue and saturation did not change. + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); + EXPECT_EQ(light->get_color_temperature(), 0); + EXPECT_EQ(light->get_hue(), 0); + EXPECT_EQ(light->get_saturation(), 0); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); // Light should have switched to RGB mode. + + light_cmd->set_has_brightness(false); + light_cmd->set_has_color_temperature(false); + light_cmd->set_hue(30); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(30); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + + // Verify light is still off, brightness is still 0 and hue and saturation did not change. + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); + EXPECT_EQ(light->get_color_temperature(), 0); + EXPECT_EQ(light->get_hue(), 0); + EXPECT_EQ(light->get_saturation(), 0); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); // Light should have switched to RGB mode. +} + +#endif // !MQTT_MANAGER_HOME_ASSISTANT_LIGHT_TEST diff --git a/docker/MQTTManager/include/thermostat/home_assistant_thermostat.hpp b/docker/MQTTManager/include/thermostat/home_assistant_thermostat.hpp new file mode 100644 index 00000000..30946827 --- /dev/null +++ b/docker/MQTTManager/include/thermostat/home_assistant_thermostat.hpp @@ -0,0 +1,18 @@ +#ifndef MQTT_MANAGER_HOME_ASSISTANT_THERMOSTAT_HPP +#define MQTT_MANAGER_HOME_ASSISTANT_THERMOSTAT_HPP + +#include "thermostat.hpp" + +class HomeAssistantThermostat : public ThermostatEntity { +public: + HomeAssistantThermostat(uint32_t thermostat_id); + ~HomeAssistantThermostat(); + void reload_config(); + void send_state_update_to_controller(); + void home_assistant_event_callback(nlohmann::json event_data); + void command_callback(NSPanelMQTTManagerCommand &command); + +private: + std::string _home_assistant_name; +}; +#endif diff --git a/docker/MQTTManager/include/thermostat/openhab_thermostat.cpp b/docker/MQTTManager/include/thermostat/openhab_thermostat.cpp new file mode 100644 index 00000000..3a8994b7 --- /dev/null +++ b/docker/MQTTManager/include/thermostat/openhab_thermostat.cpp @@ -0,0 +1,1058 @@ +#include "openhab_thermostat.hpp" +#include "database_manager/database_manager.hpp" +#include "entity/entity.hpp" +#include "mqtt_manager_config/mqtt_manager_config.hpp" +#include "thermostat/thermostat.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +uint64_t CurrentTimeMilliseconds() { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +OpenhabThermostat::OpenhabThermostat(uint32_t thermostat_id) : ThermostatEntity(thermostat_id) { + // Process Home Assistant specific details. General thermostat data is loaded in the "ThermostatEntity" constructor. + if (this->_controller != MQTT_MANAGER_ENTITY_CONTROLLER::OPENHAB) { + SPDLOG_ERROR("OpenhabThermostat has not been recognized as controlled by OPENHAB. Will stop processing thermostat."); + return; + } + + SPDLOG_DEBUG("Loaded thermostat {}::{}", this->_id, this->_name); + this->reload_config(); // Reload configuration actually load all openhab items and attach event observers. + this->send_state_update_to_nspanel(); // Send initial state to NSPanel +} + +OpenhabThermostat::~OpenhabThermostat() { + OpenhabManager::detach_event_observer(this->_openhab_target_temperature_item, boost::bind(&OpenhabThermostat::openhab_target_temperature_event_callback, this, _1)); + OpenhabManager::detach_event_observer(this->_openhab_fan_mode_item, boost::bind(&OpenhabThermostat::openhab_fan_mode_event_callback, this, _1)); + OpenhabManager::detach_event_observer(this->_openhab_mode_item, boost::bind(&OpenhabThermostat::openhab_mode_event_callback, this, _1)); + OpenhabManager::detach_event_observer(this->_openhab_preset_item, boost::bind(&OpenhabThermostat::openhab_preset_event_callback, this, _1)); + OpenhabManager::detach_event_observer(this->_openhab_swing_item, boost::bind(&OpenhabThermostat::openhab_swing_event_callback, this, _1)); + OpenhabManager::detach_event_observer(this->_openhab_swingh_item, boost::bind(&OpenhabThermostat::openhab_swingh_event_callback, this, _1)); +} + +void OpenhabThermostat::reload_config() { + ThermostatEntity::reload_config(); + + // Detach existing event observers + OpenhabManager::detach_event_observer(this->_openhab_target_temperature_item, boost::bind(&OpenhabThermostat::openhab_target_temperature_event_callback, this, _1)); + OpenhabManager::detach_event_observer(this->_openhab_fan_mode_item, boost::bind(&OpenhabThermostat::openhab_fan_mode_event_callback, this, _1)); + OpenhabManager::detach_event_observer(this->_openhab_mode_item, boost::bind(&OpenhabThermostat::openhab_mode_event_callback, this, _1)); + OpenhabManager::detach_event_observer(this->_openhab_preset_item, boost::bind(&OpenhabThermostat::openhab_preset_event_callback, this, _1)); + OpenhabManager::detach_event_observer(this->_openhab_swing_item, boost::bind(&OpenhabThermostat::openhab_swing_event_callback, this, _1)); + OpenhabManager::detach_event_observer(this->_openhab_swingh_item, boost::bind(&OpenhabThermostat::openhab_swingh_event_callback, this, _1)); + + nlohmann::json entity_data; + try { + auto thermostat = database_manager::database.get(this->_id); + entity_data = thermostat.get_entity_data_json(); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to load thermostat {}: {}", this->_id, e.what()); + return; + } + + if (entity_data.contains("openhab_temperature_item")) { + this->_openhab_target_temperature_item = entity_data["openhab_temperature_item"]; + } else { + SPDLOG_ERROR("No openhab_temperature_item name defined for Thermostat {}::{}", this->_id, this->_name); + } + + if (entity_data.contains("openhab_fan_mode_item")) { + this->_openhab_fan_mode_item = entity_data["openhab_fan_mode_item"]; + } else { + SPDLOG_WARN("No openhab_fan_mode_item name defined for Thermostat {}::{}", this->_id, this->_name); + } + + if (entity_data.contains("openhab_hvac_mode_item")) { + this->_openhab_mode_item = entity_data["openhab_hvac_mode_item"]; + } else { + SPDLOG_WARN("No openhab_hvac_mode_item name defined for Thermostat {}::{}", this->_id, this->_name); + } + + if (entity_data.contains("openhab_preset_item")) { + this->_openhab_preset_item = entity_data["openhab_preset_item"]; + } else { + SPDLOG_WARN("No openhab_preset_item name defined for Thermostat {}::{}", this->_id, this->_name); + } + + if (entity_data.contains("openhab_swing_item")) { + this->_openhab_swing_item = entity_data["openhab_swing_item"]; + } else { + SPDLOG_WARN("No openhab_swing_item name defined for Thermostat {}::{}", this->_id, this->_name); + } + + if (entity_data.contains("openhab_swingh_item")) { + this->_openhab_swingh_item = entity_data["openhab_swingh_item"]; + } else { + SPDLOG_WARN("No openhab_swingh_item name defined for Thermostat {}::{}", this->_id, this->_name); + } + + SPDLOG_DEBUG("Thermostat {}::{} Attaching openhab target temp item: {}", this->_id, this->_name, this->_openhab_target_temperature_item); + + // Attach new event observers + OpenhabManager::attach_event_observer(this->_openhab_target_temperature_item, boost::bind(&OpenhabThermostat::openhab_target_temperature_event_callback, this, _1)); + OpenhabManager::attach_event_observer(this->_openhab_fan_mode_item, boost::bind(&OpenhabThermostat::openhab_fan_mode_event_callback, this, _1)); + OpenhabManager::attach_event_observer(this->_openhab_mode_item, boost::bind(&OpenhabThermostat::openhab_mode_event_callback, this, _1)); + OpenhabManager::attach_event_observer(this->_openhab_preset_item, boost::bind(&OpenhabThermostat::openhab_preset_event_callback, this, _1)); + OpenhabManager::attach_event_observer(this->_openhab_swing_item, boost::bind(&OpenhabThermostat::openhab_swing_event_callback, this, _1)); + OpenhabManager::attach_event_observer(this->_openhab_swingh_item, boost::bind(&OpenhabThermostat::openhab_swingh_event_callback, this, _1)); +} + +void OpenhabThermostat::send_state_update_to_controller() { + nlohmann::json service_data; + service_data["type"] = "ItemCommandEvent"; + bool send_state_update = false; + + if (this->_requested_mode != this->_current_mode) { + service_data["topic"] = fmt::format("openhab/items/{}/command", this->_openhab_mode_item); + // payload_data["type"] = "string"; + nlohmann::json payload_data; + payload_data["value"] = this->_requested_mode.value; + payload_data["type"] = "String"; + service_data["payload"] = payload_data.dump(); + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { + this->_current_mode = this->_requested_mode; + this->_entity_changed_callbacks(this); + send_state_update = true; + } + OpenhabManager::send_json(service_data); + } + + if (this->_requested_fan_mode != this->_current_fan_mode) { + service_data["topic"] = fmt::format("openhab/items/{}/command", this->_openhab_fan_mode_item); + // payload_data["type"] = "string"; + nlohmann::json payload_data; + payload_data["value"] = this->_requested_fan_mode.value; + payload_data["type"] = "String"; + service_data["payload"] = payload_data.dump(); + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { + this->_current_fan_mode = this->_requested_fan_mode; + this->_entity_changed_callbacks(this); + send_state_update = true; + } + OpenhabManager::send_json(service_data); + } + + if (this->_requested_preset != this->_current_preset) { + service_data["topic"] = fmt::format("openhab/items/{}/command", this->_openhab_preset_item); + // payload_data["type"] = "string"; + nlohmann::json payload_data; + payload_data["value"] = this->_requested_preset.value; + payload_data["type"] = "String"; + service_data["payload"] = payload_data.dump(); + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { + this->_current_preset = this->_requested_preset; + this->_entity_changed_callbacks(this); + send_state_update = true; + } + OpenhabManager::send_json(service_data); + } + + if (this->_requested_swing_mode != this->_current_swing_mode) { + service_data["topic"] = fmt::format("openhab/items/{}/command", this->_openhab_swing_item); + // payload_data["type"] = "string"; + nlohmann::json payload_data; + payload_data["value"] = this->_requested_swing_mode.value; + payload_data["type"] = "String"; + service_data["payload"] = payload_data.dump(); + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { + this->_current_swing_mode = this->_requested_swing_mode; + this->_entity_changed_callbacks(this); + send_state_update = true; + } + OpenhabManager::send_json(service_data); + } + + if (this->_requested_swingh_mode != this->_current_swingh_mode) { + service_data["topic"] = fmt::format("openhab/items/{}/command", this->_openhab_swingh_item); + // payload_data["type"] = "string"; + nlohmann::json payload_data; + payload_data["value"] = this->_requested_swingh_mode.value; + payload_data["type"] = "String"; + service_data["payload"] = payload_data.dump(); + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { + this->_current_swingh_mode = this->_requested_swingh_mode; + this->_entity_changed_callbacks(this); + send_state_update = true; + } + OpenhabManager::send_json(service_data); + } + + if (this->_requested_temperature != this->_current_temperature) { + service_data["topic"] = fmt::format("openhab/items/{}/command", this->_openhab_target_temperature_item); + // payload_data["type"] = "string"; + nlohmann::json payload_data; + payload_data["value"] = this->_requested_temperature; + payload_data["type"] = "Decimal"; + service_data["payload"] = payload_data.dump(); + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE)) { + this->_current_temperature = this->_requested_temperature; + this->_entity_changed_callbacks(this); + send_state_update = true; + } + OpenhabManager::send_json(service_data); + } + + if (send_state_update) { + this->send_state_update_to_nspanel(); + } +} + +void OpenhabThermostat::openhab_target_temperature_event_callback(nlohmann::json data) { + SPDLOG_DEBUG("Thermostat {}::{} get temperature callback!", this->_id, this->_name); + + if (std::string(data["type"]).compare("ItemStateChangedEvent") == 0) { + // Extract topic into multiple parts + std::string topic = data["topic"]; + std::vector topic_parts; + boost::split(topic_parts, topic, boost::is_any_of("/")); + std::string topic_item = topic_parts[2]; + + if (topic_parts.size() < 3) { + SPDLOG_ERROR("Received ItemStateChangedEvent with a topic with not enough parts, topic: {}", std::string(data["topic"])); + return; + } + + nlohmann::json payload = nlohmann::json::parse(std::string(data["payload"])); + if (topic_item.compare(this->_openhab_target_temperature_item) == 0) { + // We only care about the first event from Openhab, ignore the rest but still indicate that event was handled so the manager stops looping over all entities. + if (CurrentTimeMilliseconds() >= this->_last_target_temperature_change + 1000) { + SPDLOG_DEBUG("Thermostat {}::{}, payload: {}", this->_id, this->_name, payload.dump()); + if (payload["value"].is_null()) { // Got state but state is NULL, ignore. + return; + } else if (payload["value"].is_string() && std::string(payload["value"]).compare("NULL") == 0) { + return; + } else if (payload["value"].is_object()) { + return; + } + + float target_temperature = std::round(atof(std::string(payload["value"]).c_str())); + SPDLOG_DEBUG("Thermostat {}::{} got new temperature {}, current temperature: {}.", this->_id, this->_name, target_temperature, this->_current_temperature); + if (target_temperature != this->_current_temperature) { + this->_current_temperature = target_temperature; + this->_requested_temperature = target_temperature; + this->_last_target_temperature_change = CurrentTimeMilliseconds(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + } + } + } + } else if (std::string(data["type"]).compare("ItemStateFetched") == 0) { + SPDLOG_TRACE("OpenHAB thermostat {}::{} Got initial data from OpenHAB via custom ItemStateFetched event.", this->_id, this->_name); + if (this->_openhab_target_temperature_item.compare(data["payload"]["name"]) == 0) { + nlohmann::json payload = data["payload"]; + if (payload["state"].is_null()) { // Got state but state is NULL, ignore. + return; + } else if (payload["state"].is_string() && std::string(payload["state"]).compare("NULL") == 0) { + return; + } else if (payload["state"].is_object()) { + return; + } + + float target_temperature = std::round(atof(std::string(payload["state"]).c_str())); + + SPDLOG_DEBUG("Thermostat {}::{} got new temperature {}, current temperature: {}.", this->_id, this->_name, target_temperature, this->_current_temperature); + if (target_temperature != this->_current_temperature) { + this->_current_temperature = target_temperature; + this->_requested_temperature = target_temperature; + this->_last_target_temperature_change = CurrentTimeMilliseconds(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + } + } + } +} + +void OpenhabThermostat::openhab_fan_mode_event_callback(nlohmann::json data) { + if (std::string(data["type"]).compare("ItemStateChangedEvent") == 0) { + // Extract topic into multiple parts + std::string topic = data["topic"]; + std::vector topic_parts; + boost::split(topic_parts, topic, boost::is_any_of("/")); + std::string topic_item = topic_parts[2]; + + if (topic_parts.size() < 3) { + SPDLOG_ERROR("Received ItemStateChangedEvent with a topic with not enough parts, topic: {}", std::string(data["topic"])); + return; + } + + nlohmann::json payload = nlohmann::json::parse(std::string(data["payload"])); + if (topic_item.compare(this->_openhab_fan_mode_item) == 0) { + // We only care about the first event from Openhab, ignore the rest but still indicate that event was handled so the manager stops looping over all entities. + if (CurrentTimeMilliseconds() >= this->_last_fan_mode_change + 1000) { + if (payload["value"].is_null()) { // Got state but state is NULL, ignore. + return; + } else if (payload["value"].is_string() && std::string(payload["value"]).compare("NULL") == 0) { + return; + } else if (payload["value"].is_object()) { + return; + } + + std::string mode = std::string(payload["value"]); + SPDLOG_DEBUG("Thermostat {}::{} got new fan mode {}, current fan mode: {}.", this->_id, this->_name, mode, this->_current_fan_mode.value); + if (this->_current_fan_mode.value.compare(mode) != 0) { + auto new_fan_mode = std::find_if(this->_supported_fan_modes.begin(), this->_supported_fan_modes.end(), [&](const ThermostatOptionHolder &option) { + return option.value.compare(mode) == 0; + }); + if (new_fan_mode != this->_supported_fan_modes.end()) { + this->_current_fan_mode = *new_fan_mode; + this->_requested_fan_mode = *new_fan_mode; + this->_last_fan_mode_change = CurrentTimeMilliseconds(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + SPDLOG_DEBUG("Thermostat {}::{} got new fan mode: {}", this->_id, this->_name, new_fan_mode->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new fan mode '{}' from openhab but that fan mode is not supported.", this->_id, this->_name, mode); + } + } + } + } + } else if (std::string(data["type"]).compare("ItemStateFetched") == 0) { + SPDLOG_TRACE("OpenHAB thermostat {}::{} Got initial data from OpenHAB via custom ItemStateFetched event.", this->_id, this->_name); + if (this->_openhab_fan_mode_item.compare(data["payload"]["name"]) == 0) { + nlohmann::json payload = data["payload"]; + if (payload["state"].is_null()) { // Got state but state is NULL, ignore. + return; + } else if (payload["state"].is_string() && std::string(payload["state"]).compare("NULL") == 0) { + return; + } else if (payload["state"].is_object()) { + return; + } + + std::string mode = std::string(payload["state"]); + SPDLOG_DEBUG("Thermostat {}::{} got new fan mode {}, current fan mode: {}.", this->_id, this->_name, mode, this->_current_fan_mode.value); + if (this->_current_fan_mode.value.compare(mode) != 0) { + auto new_fan_mode = std::find_if(this->_supported_fan_modes.begin(), this->_supported_fan_modes.end(), [&](const ThermostatOptionHolder &option) { + return option.value.compare(mode) == 0; + }); + if (new_fan_mode != this->_supported_fan_modes.end()) { + this->_current_fan_mode = *new_fan_mode; + this->_requested_fan_mode = *new_fan_mode; + this->_last_fan_mode_change = CurrentTimeMilliseconds(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + SPDLOG_DEBUG("Thermostat {}::{} got new fan mode: {}", this->_id, this->_name, new_fan_mode->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new fan mode '{}' from openhab but that fan mode is not supported.", this->_id, this->_name, mode); + } + } + } + } +} + +void OpenhabThermostat::openhab_mode_event_callback(nlohmann::json data) { + if (std::string(data["type"]).compare("ItemStateChangedEvent") == 0) { + // Extract topic into multiple parts + std::string topic = data["topic"]; + std::vector topic_parts; + boost::split(topic_parts, topic, boost::is_any_of("/")); + std::string topic_item = topic_parts[2]; + + if (topic_parts.size() < 3) { + SPDLOG_ERROR("Received ItemStateChangedEvent with a topic with not enough parts, topic: {}", std::string(data["topic"])); + return; + } + + nlohmann::json payload = nlohmann::json::parse(std::string(data["payload"])); + if (topic_item.compare(this->_openhab_mode_item) == 0) { + // We only care about the first event from Openhab, ignore the rest but still indicate that event was handled so the manager stops looping over all entities. + if (CurrentTimeMilliseconds() >= this->_last_mode_change + 1000) { + if (payload["value"].is_null()) { // Got state but state is NULL, ignore. + return; + } else if (payload["value"].is_string() && std::string(payload["value"]).compare("NULL") == 0) { + return; + } else if (payload["value"].is_object()) { + return; + } + + std::string mode = std::string(payload["value"]); + SPDLOG_DEBUG("Thermostat {}::{} got new mode {}, current mode: {}.", this->_id, this->_name, mode, this->_current_mode.value); + if (this->_current_mode.value.compare(mode) != 0) { + auto new_mode = std::find_if(this->_supported_modes.begin(), this->_supported_modes.end(), [&](const ThermostatOptionHolder &option) { + return option.value.compare(mode) == 0; + }); + if (new_mode != this->_supported_modes.end()) { + this->_current_mode = *new_mode; + this->_requested_mode = *new_mode; + this->_last_mode_change = CurrentTimeMilliseconds(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + SPDLOG_DEBUG("Thermostat {}::{} got new mode: {}", this->_id, this->_name, new_mode->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new mode '{}' from openhab but that HVAC mode is not supported.", this->_id, this->_name, mode); + } + } + } + } + } else if (std::string(data["type"]).compare("ItemStateFetched") == 0) { + SPDLOG_TRACE("OpenHAB thermostat {}::{} Got initial data from OpenHAB via custom ItemStateFetched event.", this->_id, this->_name); + if (this->_openhab_mode_item.compare(data["payload"]["name"]) == 0) { + nlohmann::json payload = data["payload"]; + if (payload["state"].is_null()) { // Got state but state is NULL, ignore. + return; + } else if (payload["state"].is_string() && std::string(payload["state"]).compare("NULL") == 0) { + return; + } else if (payload["state"].is_object()) { + return; + } + + std::string mode = std::string(payload["state"]); + SPDLOG_DEBUG("Thermostat {}::{} got new mode {}, current mode: {}.", this->_id, this->_name, mode, this->_current_mode.value); + if (this->_current_mode.value.compare(mode) != 0) { + auto new_mode = std::find_if(this->_supported_modes.begin(), this->_supported_modes.end(), [&](const ThermostatOptionHolder &option) { + return option.value.compare(mode) == 0; + }); + if (new_mode != this->_supported_modes.end()) { + this->_current_mode = *new_mode; + this->_requested_mode = *new_mode; + this->_last_mode_change = CurrentTimeMilliseconds(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + SPDLOG_DEBUG("Thermostat {}::{} got new mode: {}", this->_id, this->_name, new_mode->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new mode '{}' from openhab but that HVAC mode is not supported.", this->_id, this->_name, mode); + } + } + } + } +} + +void OpenhabThermostat::openhab_preset_event_callback(nlohmann::json data) { + if (std::string(data["type"]).compare("ItemStateChangedEvent") == 0) { + // Extract topic into multiple parts + std::string topic = data["topic"]; + std::vector topic_parts; + boost::split(topic_parts, topic, boost::is_any_of("/")); + std::string topic_item = topic_parts[2]; + + if (topic_parts.size() < 3) { + SPDLOG_ERROR("Received ItemStateChangedEvent with a topic with not enough parts, topic: {}", std::string(data["topic"])); + return; + } + + nlohmann::json payload = nlohmann::json::parse(std::string(data["payload"])); + if (topic_item.compare(this->_openhab_preset_item) == 0) { + // We only care about the first event from Openhab, ignore the rest but still indicate that event was handled so the manager stops looping over all entities. + if (CurrentTimeMilliseconds() >= this->_last_preset_change + 1000) { + if (payload["value"].is_null()) { // Got state but state is NULL, ignore. + return; + } else if (payload["value"].is_string() && std::string(payload["value"]).compare("NULL") == 0) { + return; + } else if (payload["value"].is_object()) { + return; + } + + std::string mode = std::string(payload["value"]); + SPDLOG_DEBUG("Thermostat {}::{} got new preset {}, current preset: {}.", this->_id, this->_name, mode, this->_current_preset.value); + if (this->_current_preset.value.compare(mode) != 0) { + auto new_preset = std::find_if(this->_supported_presets.begin(), this->_supported_presets.end(), [&](const ThermostatOptionHolder &option) { + return option.value.compare(mode) == 0; + }); + if (new_preset != this->_supported_presets.end()) { + this->_current_preset = *new_preset; + this->_requested_preset = *new_preset; + this->_last_preset_change = CurrentTimeMilliseconds(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + SPDLOG_DEBUG("Thermostat {}::{} got new preset: {}", this->_id, this->_name, new_preset->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new preset '{}' from HA but that preset is not supported.", this->_id, this->_name, mode); + } + } + } + } + } else if (std::string(data["type"]).compare("ItemStateFetched") == 0) { + SPDLOG_TRACE("OpenHAB thermostat {}::{} Got initial data from OpenHAB via custom ItemStateFetched event.", this->_id, this->_name); + if (this->_openhab_preset_item.compare(data["payload"]["name"]) == 0) { + nlohmann::json payload = data["payload"]; + if (payload["state"].is_null()) { // Got state but state is NULL, ignore. + return; + } else if (payload["state"].is_string() && std::string(payload["state"]).compare("NULL") == 0) { + return; + } else if (payload["state"].is_object()) { + return; + } + + std::string mode = std::string(payload["state"]); + SPDLOG_DEBUG("Thermostat {}::{} got new mode {}, current mode: {}.", this->_id, this->_name, mode, this->_current_mode.value); + if (this->_current_mode.value.compare(mode) != 0) { + auto new_preset = std::find_if(this->_supported_presets.begin(), this->_supported_presets.end(), [&](const ThermostatOptionHolder &option) { + return option.value.compare(mode) == 0; + }); + if (new_preset != this->_supported_presets.end()) { + this->_current_preset = *new_preset; + this->_requested_preset = *new_preset; + this->_last_preset_change = CurrentTimeMilliseconds(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + SPDLOG_DEBUG("Thermostat {}::{} got new preset: {}", this->_id, this->_name, new_preset->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new preset '{}' from openhab but that preset is not supported.", this->_id, this->_name, mode); + } + } + } + } +} + +void OpenhabThermostat::openhab_swing_event_callback(nlohmann::json data) { + if (std::string(data["type"]).compare("ItemStateChangedEvent") == 0) { + // Extract topic into multiple parts + std::string topic = data["topic"]; + std::vector topic_parts; + boost::split(topic_parts, topic, boost::is_any_of("/")); + std::string topic_item = topic_parts[2]; + + if (topic_parts.size() < 3) { + SPDLOG_ERROR("Received ItemStateChangedEvent with a topic with not enough parts, topic: {}", std::string(data["topic"])); + return; + } + + nlohmann::json payload = nlohmann::json::parse(std::string(data["payload"])); + if (topic_item.compare(this->_openhab_swing_item) == 0) { + // We only care about the first event from Openhab, ignore the rest but still indicate that event was handled so the manager stops looping over all entities. + if (CurrentTimeMilliseconds() >= this->_last_swing_change + 1000) { + if (payload["value"].is_null()) { // Got state but state is NULL, ignore. + return; + } else if (payload["value"].is_string() && std::string(payload["value"]).compare("NULL") == 0) { + return; + } else if (payload["value"].is_object()) { + return; + } + + std::string mode = std::string(payload["value"]); + SPDLOG_DEBUG("Thermostat {}::{} got new swing {}, current swing: {}.", this->_id, this->_name, mode, this->_current_swing_mode.value); + if (this->_current_swing_mode.value.compare(mode) != 0) { + auto new_swing = std::find_if(this->_supported_swing_modes.begin(), this->_supported_swing_modes.end(), [&](const ThermostatOptionHolder &option) { + return option.value.compare(mode) == 0; + }); + if (new_swing != this->_supported_swing_modes.end()) { + this->_current_swing_mode = *new_swing; + this->_requested_swing_mode = *new_swing; + this->_last_swing_change = CurrentTimeMilliseconds(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + SPDLOG_DEBUG("Thermostat {}::{} got new swing mode: {}", this->_id, this->_name, new_swing->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new swing mode '{}' from openhab but that swing mode is not supported.", this->_id, this->_name, mode); + } + } + } + } + } else if (std::string(data["type"]).compare("ItemStateFetched") == 0) { + SPDLOG_TRACE("OpenHAB thermostat {}::{} Got initial data from OpenHAB via custom ItemStateFetched event.", this->_id, this->_name); + if (this->_openhab_swing_item.compare(data["payload"]["name"]) == 0) { + nlohmann::json payload = data["payload"]; + if (payload["state"].is_null()) { // Got state but state is NULL, ignore. + return; + } else if (payload["state"].is_string() && std::string(payload["state"]).compare("NULL") == 0) { + return; + } else if (payload["state"].is_object()) { + return; + } + + std::string mode = std::string(payload["state"]); + SPDLOG_DEBUG("Thermostat {}::{} got new swing mode {}, current swing mode: {}.", this->_id, this->_name, mode, this->_current_swing_mode.value); + if (this->_current_swing_mode.value.compare(mode) != 0) { + auto new_swing_mode = std::find_if(this->_supported_swing_modes.begin(), this->_supported_swing_modes.end(), [&](const ThermostatOptionHolder &option) { + return option.value.compare(mode) == 0; + }); + if (new_swing_mode != this->_supported_swing_modes.end()) { + this->_current_swing_mode = *new_swing_mode; + this->_requested_swing_mode = *new_swing_mode; + this->_last_swing_change = CurrentTimeMilliseconds(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + SPDLOG_DEBUG("Thermostat {}::{} got new swing mode: {}", this->_id, this->_name, new_swing_mode->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new swing mode '{}' from openhab but that swing mode is not supported.", this->_id, this->_name, mode); + } + } + } + } +} + +void OpenhabThermostat::openhab_swingh_event_callback(nlohmann::json data) { + if (std::string(data["type"]).compare("ItemStateChangedEvent") == 0) { + // Extract topic into multiple parts + std::string topic = data["topic"]; + std::vector topic_parts; + boost::split(topic_parts, topic, boost::is_any_of("/")); + std::string topic_item = topic_parts[2]; + + if (topic_parts.size() < 3) { + SPDLOG_ERROR("Received ItemStateChangedEvent with a topic with not enough parts, topic: {}", std::string(data["topic"])); + return; + } + + nlohmann::json payload = nlohmann::json::parse(std::string(data["payload"])); + if (topic_item.compare(this->_openhab_swingh_item) == 0) { + // We only care about the first event from Openhab, ignore the rest but still indicate that event was handled so the manager stops looping over all entities. + if (CurrentTimeMilliseconds() >= this->_last_swingh_change + 1000) { + if (payload["value"].is_null()) { // Got state but state is NULL, ignore. + return; + } else if (payload["value"].is_string() && std::string(payload["value"]).compare("NULL") == 0) { + return; + } else if (payload["value"].is_object()) { + return; + } + + std::string mode = std::string(payload["value"]); + SPDLOG_DEBUG("Thermostat {}::{} got new horizontal swing {}, current horizontal swing: {}.", this->_id, this->_name, mode, this->_current_swingh_mode.value); + if (this->_current_swingh_mode.value.compare(mode) != 0) { + auto new_horizontal_swing = std::find_if(this->_supported_swingh_modes.begin(), this->_supported_swingh_modes.end(), [&](const ThermostatOptionHolder &option) { + return option.value.compare(mode) == 0; + }); + if (new_horizontal_swing != this->_supported_swingh_modes.end()) { + this->_current_swingh_mode = *new_horizontal_swing; + this->_requested_swingh_mode = *new_horizontal_swing; + this->_last_swingh_change = CurrentTimeMilliseconds(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + SPDLOG_DEBUG("Thermostat {}::{} got new horizontal swing mode: {}", this->_id, this->_name, new_horizontal_swing->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new horizontal swing mode '{}' from openhab but that mode is not supported.", this->_id, this->_name, mode); + } + } + } + } + } else if (std::string(data["type"]).compare("ItemStateFetched") == 0) { + SPDLOG_TRACE("OpenHAB thermostat {}::{} Got initial data from OpenHAB via custom ItemStateFetched event.", this->_id, this->_name); + if (this->_openhab_swing_item.compare(data["payload"]["name"]) == 0) { + nlohmann::json payload = data["payload"]; + if (payload["state"].is_null()) { // Got state but state is NULL, ignore. + return; + } else if (payload["state"].is_string() && std::string(payload["state"]).compare("NULL") == 0) { + return; + } else if (payload["state"].is_object()) { + return; + } + + std::string mode = std::string(payload["state"]); + SPDLOG_DEBUG("Thermostat {}::{} got new horizontal swing mode {}, current horizontal swing mode: {}.", this->_id, this->_name, mode, this->_current_swingh_mode.value); + if (this->_current_swingh_mode.value.compare(mode) != 0) { + auto new_swing_mode = std::find_if(this->_supported_swingh_modes.begin(), this->_supported_swingh_modes.end(), [&](const ThermostatOptionHolder &option) { + return option.value.compare(mode) == 0; + }); + if (new_swing_mode != this->_supported_swingh_modes.end()) { + this->_current_swingh_mode = *new_swing_mode; + this->_requested_swingh_mode = *new_swing_mode; + this->_last_swingh_change = CurrentTimeMilliseconds(); + this->send_state_update_to_nspanel(); + this->_signal_entity_changed(); + SPDLOG_DEBUG("Thermostat {}::{} got new horizontal swing mode: {}", this->_id, this->_name, new_swing_mode->value); + } else { + SPDLOG_WARN("Thermostat {}::{} got new horizontal swing mode '{}' from openhab but that mode is not supported.", this->_id, this->_name, mode); + } + } + } + } +} + +// TESTING +#if defined(TEST_MODE) && TEST_MODE == 1 +#include + +class HomeAssistantLightTest : public ::testing::Test { +protected: + HomeAssistantLightTest() { + // Initialize any necessary resources or setup for the tests + } + + void SetUp() override { + + // Initialize any necessary resources or setup for the tests + database_manager::Entity light_entity; + light_entity.entity_type = "light"; + light_entity.friendly_name = "Unit test HA light (light)"; + light_entity.room_id = 1; + light_entity.room_view_position = 2; + light_entity.set_entity_data_json({{"can_color_temperature", true}, + {"can_dim", true}, + {"can_rgb", true}, + {"controlled_by_nspanel_main_page", true}, + {"controller", "home_assistant"}, + {"home_assistant_name", "light.office_ceiling_light"}, + {"is_ceiling_light", true}, + {"openhab_control_mode", "dimmer"}, + {"openhab_item_color_temp", ""}, + {"openhab_item_dimmer", ""}, + {"openhab_item_rgb", ""}, + {"openhab_item_switch", ""}, + {"openhab_name", ""}}); + ha_light_id = database_manager::database.insert(light_entity); + // light_entity = database_manager::database.get(ha_light_id); + + SPDLOG_INFO("HA Light created in DB. Creating instance of light object."); + light = new HomeAssistantLight(ha_light_id); + } + + void TearDown() override { + // Clean up any resources or teardown after the tests + + database_manager::database.remove(ha_light_id); + // database_manager::database.remove(ha_light_switch_id); + } + + HomeAssistantLight *light = nullptr; + + int32_t ha_light_id; + int32_t ha_light_switch_id; +}; + +TEST_F(HomeAssistantLightTest, is_off_by_default) { + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); +} + +TEST_F(HomeAssistantLightTest, is_not_on_after_turn_on_in_nonoptimistic_mode) { + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "false"); + light->turn_on(false); + light->set_brightness(50, true); + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); +} + +TEST_F(HomeAssistantLightTest, is_on_after_turn_on_in_optimistic_mode) { + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + light->turn_on(false); + light->set_brightness(50, true); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); +} + +TEST_F(HomeAssistantLightTest, light_reacts_to_state_changes_from_home_assistant) { + // Enable optimistic mode for this test + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + + nlohmann::json event_data = nlohmann::json::parse(R"( + { + + "event":{ + "context":{ + "id":"01K2WNR8WT8VS6B5HTQ7ZEKSAP", + "parent_id":null, + "user_id":"d28bd745ad714c108c27c31a90406189" + }, + "data":{ + "entity_id":"light.office_ceiling_light", + "new_state":{ + "attributes":{ + "brightness":204, + "color_mode":"color_temp", + "color_temp":443, + "color_temp_kelvin":2257, + "effect":null, + "effect_list":[ + "blink", + "breathe", + "okay", + "channel_change", + "finish_effect", + "stop_effect" + ], + "friendly_name":"office_ceiling_light", + "hs_color":[ + 20, + 80 + ], + "max_color_temp_kelvin":4000, + "max_mireds":454, + "min_color_temp_kelvin":2202, + "min_mireds":250, + "rgb_color":[ + 255, + 149, + 46 + ], + "supported_color_modes":[ + "color_temp" + ], + "supported_features":44, + "xy_color":[ + 0.572, + 0.389 + ] + }, + "context":{ + "id":"01K2WNR8WT8VS6B5HTQ7ZEKSAP", + "parent_id":null, + "user_id":"d28bd745ad714c108c27c31a90406189" + }, + "entity_id":"light.office_ceiling_light", + "last_changed":"2025-08-17T18:45:35.203504+00:00", + "last_reported":"2025-08-17T18:48:00.253850+00:00", + "last_updated":"2025-08-17T18:48:00.253850+00:00", + "state":"on" + }, + "old_state": "removed_as_it_is_not_used" + }, + "event_type":"state_changed", + "origin":"LOCAL", + "time_fired":"2025-08-17T18:48:00.253850+00:00" + }, + "id":3, + "type":"event" + + } )"); + + light->home_assistant_event_callback(event_data); + + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 80); // We sent brightness 204, ie. 80% + EXPECT_EQ(light->get_color_temperature(), 2257); + EXPECT_EQ(light->get_color_temperature(), 2257); + + event_data["event"]["data"]["new_state"]["attributes"]["brightness"] = 153; + event_data["event"]["data"]["new_state"]["attributes"]["color_temp_kelvin"] = 5000; + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); // We sent brightness 153, ie. 60% + EXPECT_EQ(light->get_color_temperature(), 5000); + + event_data["event"]["data"]["new_state"]["state"] = "off"; + event_data["event"]["data"]["new_state"]["attributes"]["brightness"] = 0; + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 60); // Remember last brightness value + + // Change to RGB mode + event_data["event"]["data"]["new_state"]["state"] = "on"; + event_data["event"]["data"]["new_state"]["attributes"]["brightness"] = 153; // 60% + event_data["event"]["data"]["new_state"]["attributes"]["color_mode"] = "xy"; // TODO: Update with correct color mode as sent from HA. + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); + EXPECT_EQ(light->get_hue(), 20); + EXPECT_EQ(light->get_saturation(), 80); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that changes to RGB/HSB values as correctly interpreted + event_data["event"]["data"]["new_state"]["attributes"]["hs_color"][0] = 30; + event_data["event"]["data"]["new_state"]["attributes"]["hs_color"][1] = 60; + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); + EXPECT_EQ(light->get_hue(), 30); + EXPECT_EQ(light->get_saturation(), 60); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that we can revert back to color temp mode + event_data["event"]["data"]["new_state"]["state"] = "on"; + event_data["event"]["data"]["new_state"]["attributes"]["brightness"] = 153; // 60% + event_data["event"]["data"]["new_state"]["attributes"]["color_mode"] = "color_temp"; + event_data["event"]["data"]["new_state"]["attributes"]["color_temp_kelvin"] = 4000; + light->home_assistant_event_callback(event_data); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 60); + EXPECT_EQ(light->get_color_temperature(), 4000); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); +} + +#define COLOR_TEMP_PERCENT_TO_KELVIN(min, max, kelvin_pct) (((max - min) / 100) * kelvin_pct + min) +TEST_F(HomeAssistantLightTest, verify_nspanel_command_compliance) { + // Enable optimistic mode for this test + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + // spdlog::set_level(spdlog::level::trace); + + NSPanelMQTTManagerCommand cmd; + cmd.set_nspanel_id(0); + NSPanelMQTTManagerCommand_LightCommand *light_cmd = cmd.mutable_light_command(); + std::vector light_ids = {light->get_id()}; + light_cmd->add_light_ids(light->get_id()); + light_cmd->set_brightness(50); + light_cmd->set_has_brightness(true); + light_cmd->set_color_temperature(50); + light_cmd->set_has_color_temperature(true); + light_cmd->set_has_hue(false); + light_cmd->set_has_saturation(false); + + light->command_callback(cmd); + + // Check that light turns on, sets correct brightness and color temperature. + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 50)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + // Verify that only color temperature can be change. + light_cmd->set_has_brightness(false); + light_cmd->set_color_temperature(70); + light_cmd->set_has_color_temperature(true); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + // Verify that only brightness can be change. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(true); + light_cmd->set_brightness(70); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + // Verify that hue and saturation can be changed and the lights goes into RGB mode. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(false); + light_cmd->set_hue(30); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(60); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 30); + EXPECT_EQ(light->get_saturation(), 60); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that only hue can be changed. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(false); + light_cmd->set_hue(40); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(60); + light_cmd->set_has_saturation(false); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 40); + EXPECT_EQ(light->get_saturation(), 60); // Verify that value is unchanged. + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that only saturation can be changed. + light_cmd->set_has_color_temperature(false); + light_cmd->set_has_brightness(false); + light_cmd->set_hue(40); + light_cmd->set_has_hue(false); + light_cmd->set_saturation(70); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 70); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 40); // Verify that value is unchanged. + EXPECT_EQ(light->get_saturation(), 70); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + // Verify that brightness can be changed and RGB is kept. + light_cmd->set_has_color_temperature(false); + light_cmd->set_brightness(100); + light_cmd->set_has_brightness(true); + light_cmd->set_hue(40); + light_cmd->set_has_hue(false); + light_cmd->set_saturation(70); + light_cmd->set_has_saturation(false); + light->command_callback(cmd); + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 100); // Verify that value is unchanged. + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 70)); // Verify that value is unchanged. + EXPECT_EQ(light->get_hue(), 40); // Verify that value is unchanged. + EXPECT_EQ(light->get_saturation(), 70); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); + + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); +} + +TEST_F(HomeAssistantLightTest, verify_optimistic_mode_compliance) { + // Enable optimistic mode for this test + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "true"); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + // spdlog::set_level(spdlog::level::trace); + + NSPanelMQTTManagerCommand cmd; + cmd.set_nspanel_id(0); + NSPanelMQTTManagerCommand_LightCommand *light_cmd = cmd.mutable_light_command(); + std::vector light_ids = {light->get_id()}; + light_cmd->add_light_ids(light->get_id()); + light_cmd->set_brightness(50); + light_cmd->set_has_brightness(true); + light_cmd->set_color_temperature(50); + light_cmd->set_has_color_temperature(true); + light_cmd->set_has_hue(false); + light_cmd->set_has_saturation(false); + + light->command_callback(cmd); + + // Check that light turns on, sets correct brightness and color temperature. + EXPECT_EQ(light->get_state(), true); + EXPECT_EQ(light->get_brightness(), 50); + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 50)); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); + + light_cmd->set_has_brightness(false); + light_cmd->set_has_color_temperature(false); + light_cmd->set_hue(30); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(50); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + + EXPECT_EQ(light->get_state(), true); // Verify light is still on + EXPECT_EQ(light->get_brightness(), 50); // Verify light is still set to 50% brightness + EXPECT_EQ(light->get_color_temperature(), COLOR_TEMP_PERCENT_TO_KELVIN(color_temp_min, color_temp_max, 50)); // Verify color temp has not changed. + EXPECT_EQ(light->get_hue(), 30); + EXPECT_EQ(light->get_saturation(), 50); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::RGB); // Light should have switched to RGB mode. +} + +TEST_F(HomeAssistantLightTest, verify_non_optimistic_mode_compliance) { + // Turn off optimistic mode and verify that no values change. + MqttManagerConfig::set_setting_value(MQTT_MANAGER_SETTING::OPTIMISTIC_MODE, "false"); + uint32_t color_temp_min = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MIN); + uint32_t color_temp_max = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::COLOR_TEMP_MAX); + // spdlog::set_level(spdlog::level::trace); + + NSPanelMQTTManagerCommand cmd; + cmd.set_nspanel_id(0); + NSPanelMQTTManagerCommand_LightCommand *light_cmd = cmd.mutable_light_command(); + std::vector light_ids = {light->get_id()}; + light_cmd->add_light_ids(light->get_id()); + light_cmd->set_brightness(50); + light_cmd->set_has_brightness(true); + light_cmd->set_color_temperature(50); + light_cmd->set_has_color_temperature(true); + light_cmd->set_has_hue(false); + light_cmd->set_has_saturation(false); + light->command_callback(cmd); + + // Verify light is still off, brightness is still 0 and hue and saturation did not change. + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); + EXPECT_EQ(light->get_color_temperature(), 0); + EXPECT_EQ(light->get_hue(), 0); + EXPECT_EQ(light->get_saturation(), 0); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); // Light should have switched to RGB mode. + + light_cmd->set_has_brightness(false); + light_cmd->set_has_color_temperature(false); + light_cmd->set_hue(30); + light_cmd->set_has_hue(true); + light_cmd->set_saturation(30); + light_cmd->set_has_saturation(true); + light->command_callback(cmd); + + // Verify light is still off, brightness is still 0 and hue and saturation did not change. + EXPECT_EQ(light->get_state(), false); + EXPECT_EQ(light->get_brightness(), 0); + EXPECT_EQ(light->get_color_temperature(), 0); + EXPECT_EQ(light->get_hue(), 0); + EXPECT_EQ(light->get_saturation(), 0); + EXPECT_EQ(light->get_mode(), MQTT_MANAGER_LIGHT_MODE::DEFAULT); // Light should have switched to RGB mode. +} + +#endif // !MQTT_MANAGER_HOME_ASSISTANT_LIGHT_TEST diff --git a/docker/MQTTManager/include/thermostat/openhab_thermostat.hpp b/docker/MQTTManager/include/thermostat/openhab_thermostat.hpp new file mode 100644 index 00000000..cc13e0aa --- /dev/null +++ b/docker/MQTTManager/include/thermostat/openhab_thermostat.hpp @@ -0,0 +1,36 @@ +#ifndef MQTT_MANAGER_OPENHAB_THERMOSTAT_HPP +#define MQTT_MANAGER_OPENHAB_THERMOSTAT_HPP + +#include "thermostat.hpp" + +class OpenhabThermostat : public ThermostatEntity { +public: + OpenhabThermostat(uint32_t thermostat_id); + ~OpenhabThermostat(); + void reload_config(); + void send_state_update_to_controller(); + void openhab_target_temperature_event_callback(nlohmann::json event_data); // When target temperature changes + void openhab_fan_mode_event_callback(nlohmann::json event_data); // When fan mode changes + void openhab_mode_event_callback(nlohmann::json event_data); // When mode changes + void openhab_preset_event_callback(nlohmann::json event_data); // When preset changes + void openhab_swing_event_callback(nlohmann::json event_data); // When swing changes + void openhab_swingh_event_callback(nlohmann::json event_data); // When horizontal swing changes + void command_callback(NSPanelMQTTManagerCommand &command); + +private: + std::string _openhab_target_temperature_item; + std::string _openhab_fan_mode_item; + std::string _openhab_mode_item; + std::string _openhab_preset_item; + std::string _openhab_swing_item; + std::string _openhab_swingh_item; + + uint64_t _last_target_temperature_change; + uint64_t _last_fan_mode_change; + uint64_t _last_mode_change; + uint64_t _last_preset_change; + uint64_t _last_swing_change; + uint64_t _last_swingh_change; +}; + +#endif diff --git a/docker/MQTTManager/include/thermostat/thermostat.cpp b/docker/MQTTManager/include/thermostat/thermostat.cpp new file mode 100644 index 00000000..a451d047 --- /dev/null +++ b/docker/MQTTManager/include/thermostat/thermostat.cpp @@ -0,0 +1,564 @@ +#include "thermostat/thermostat.hpp" +#include "command_manager/command_manager.hpp" +#include "database_manager/database_manager.hpp" +#include "entity_manager/entity_manager.hpp" +#include "mqtt_manager_config/mqtt_manager_config.hpp" +#include "protobuf_general.pb.h" +#include "protobuf_nspanel.pb.h" +#include "protobuf_nspanel_entity.pb.h" +#include "room/room.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ThermostatEntity::ThermostatEntity(uint32_t light_id) { + this->_id = light_id; + this->_step_size = 1.0; // Set default step size to only whole numbers. + this->_current_fan_mode.value.clear(); // Clear current mode to indicate no mode selected + this->_current_mode.value.clear(); // Clear current mode to indicate no mode selected + this->_current_swing_mode.value.clear(); // Clear current mode to indicate no mode selected + this->_current_swingh_mode.value.clear(); // Clear current mode to indicate no mode selected + this->_current_preset.value.clear(); // Clear current mode to indicate no mode selected + + this->reload_config(); + + // Build MQTT Topics + std::string mqtt_base_topic = fmt::format("nspanel/entities/light/{}/", this->_id); + CommandManager::attach_callback(boost::bind(&ThermostatEntity::command_callback, this, _1)); + + SPDLOG_DEBUG("Switch {}::{} base loaded.", this->_id, this->_name); +} + +uint16_t ThermostatEntity::get_room_id() { + return this->_room_id; +} + +void ThermostatEntity::reload_config() { + auto switch_entity = database_manager::database.get(this->_id); + this->_name = switch_entity.friendly_name; + SPDLOG_DEBUG("Loading thermostat {}::{}.", this->_id, this->_name); + + this->_room_id = switch_entity.room_id; + this->_entity_page_id = switch_entity.entities_page_id; + this->_entity_page_slot = switch_entity.room_view_position; + + nlohmann::json entity_data = switch_entity.get_entity_data_json(); + if (entity_data.contains("controller")) { + std::string controller = entity_data["controller"]; + if (controller.compare("home_assistant") == 0) { + this->_controller = MQTT_MANAGER_ENTITY_CONTROLLER::HOME_ASSISTANT; + } else if (controller.compare("openhab") == 0) { + this->_controller = MQTT_MANAGER_ENTITY_CONTROLLER::OPENHAB; + } else { + SPDLOG_ERROR("Got unknown controller ({}) for light {}::{}. Will default to HOME_ASSISTANT.", std::string(controller), this->_id, this->_name); + this->_controller = MQTT_MANAGER_ENTITY_CONTROLLER::HOME_ASSISTANT; + } + } else { + SPDLOG_ERROR("No controller defined for light {}::{}. Will default to HOME_ASSISTANT.", this->_id, this->_name); + this->_controller = MQTT_MANAGER_ENTITY_CONTROLLER::HOME_ASSISTANT; + } + + if (entity_data.contains("step_size")) { + try { + this->_step_size = std::atof(entity_data.at("step_size").get().c_str()); + } catch (std::exception &ex) { + SPDLOG_ERROR("Caught exception while trying to set step size for {}::{}. Error: {}", this->_id, this->_name, ex.what()); + } + } + + this->_supported_fan_modes.clear(); + this->_supported_modes.clear(); + this->_supported_swing_modes.clear(); + this->_supported_swingh_modes.clear(); + this->_supported_presets.clear(); + + SPDLOG_DEBUG("Loading fan modes for {}::{}.", this->_id, this->_name); + if (entity_data.contains("fan_modes")) { + for (auto &mode : entity_data["fan_modes"]) { + if (mode.contains("value")) { + ThermostatOptionHolder holder; + holder.value = mode["value"]; + if (mode.contains("icon")) { + holder.icon = mode["icon"]; + } else [[unlikely]] { + SPDLOG_ERROR("Mode does not contain an icon key."); + } + if (mode.contains("label")) { + holder.label = mode["label"]; + } else [[unlikely]] { + SPDLOG_WARN("Thermostat entry for mode {} does not give a label. Will use raw mode value.", holder.value); + holder.label = mode; + } + this->_supported_fan_modes.push_back(holder); + } else { + SPDLOG_ERROR("Mode does not contain a value key."); + } + } + + // No mode selected, default to first mode + if (!this->_supported_fan_modes.empty() && this->_current_fan_mode.value.empty()) { + SPDLOG_WARN("Thermostat {}::{} fan mode not set, will default to: {}.", this->_id, this->_name, this->_supported_fan_modes[0].label); + this->_current_fan_mode = this->_supported_fan_modes[0]; + this->_requested_fan_mode = this->_supported_fan_modes[0]; + } + } + + SPDLOG_DEBUG("Loading HVAC modes for {}::{}.", this->_id, this->_name); + if (entity_data.contains("hvac_modes")) { + for (auto &mode : entity_data["hvac_modes"]) { + if (mode.contains("value")) { + ThermostatOptionHolder holder; + holder.value = mode["value"]; + if (mode.contains("icon")) { + holder.icon = mode["icon"]; + } else [[unlikely]] { + SPDLOG_ERROR("Mode does not contain an icon key."); + } + if (mode.contains("label")) { + holder.label = mode["label"]; + } else [[unlikely]] { + SPDLOG_WARN("Thermostat entry for mode {} does not give a label. Will use raw mode value.", holder.value); + holder.label = mode; + } + this->_supported_modes.push_back(holder); + } else { + SPDLOG_ERROR("Mode does not contain a value key."); + } + } + + // No mode selected, default to first mode + if (!this->_supported_modes.empty() && this->_current_mode.value.empty()) { + SPDLOG_WARN("Thermostat {}::{} mode not set, will default to: {}.", this->_id, this->_name, this->_supported_modes[0].label); + this->_current_mode = this->_supported_modes[0]; + this->_requested_mode = this->_supported_modes[0]; + } + } + + SPDLOG_DEBUG("Loading presets for {}::{}.", this->_id, this->_name); + if (entity_data.contains("preset_modes")) { + for (auto &mode : entity_data["preset_modes"]) { + if (mode.contains("value")) { + ThermostatOptionHolder holder; + holder.value = mode["value"]; + if (mode.contains("icon")) { + holder.icon = mode["icon"]; + } else [[unlikely]] { + SPDLOG_ERROR("Mode does not contain an icon key."); + } + if (mode.contains("label")) { + holder.label = mode["label"]; + } else [[unlikely]] { + SPDLOG_WARN("Thermostat entry for mode {} does not give a label. Will use raw mode value.", holder.value); + holder.label = mode; + } + this->_supported_presets.push_back(holder); + } else { + SPDLOG_ERROR("Mode does not contain a value key."); + } + } + + // No mode selected, default to first mode + if (!this->_supported_presets.empty() && this->_current_preset.value.empty()) { + SPDLOG_WARN("Thermostat {}::{} preset mode not set, will default to: {}.", this->_id, this->_name, this->_supported_presets[0].label); + this->_current_preset = this->_supported_presets[0]; + this->_requested_preset = this->_supported_presets[0]; + } + } + + SPDLOG_DEBUG("Loading swing modes for {}::{}.", this->_id, this->_name); + if (entity_data.contains("swing_modes")) { + for (auto &mode : entity_data["swing_modes"]) { + if (mode.contains("value")) { + ThermostatOptionHolder holder; + holder.value = mode["value"]; + if (mode.contains("icon")) { + holder.icon = mode["icon"]; + } else [[unlikely]] { + SPDLOG_ERROR("Mode does not contain an icon key."); + } + if (mode.contains("label")) { + holder.label = mode["label"]; + } else [[unlikely]] { + SPDLOG_WARN("Thermostat entry for mode {} does not give a label. Will use raw mode value.", holder.value); + holder.label = mode; + } + this->_supported_swing_modes.push_back(holder); + } else { + SPDLOG_ERROR("Mode does not contain a value key."); + } + + // No mode selected, default to first mode + if (!this->_supported_swing_modes.empty() && this->_current_swing_mode.value.empty()) { + SPDLOG_WARN("Thermostat {}::{} swing mode not set, will default to: {}.", this->_id, this->_name, this->_supported_swing_modes[0].label); + this->_current_swing_mode = this->_supported_swing_modes[0]; + this->_requested_swing_mode = this->_supported_swing_modes[0]; + } + } + } + + SPDLOG_DEBUG("Loading horizontal swing modes for {}::{}.", this->_id, this->_name); + if (entity_data.contains("swingh_modes")) { + for (auto &mode : entity_data["swingh_modes"]) { + if (mode.contains("value")) { + ThermostatOptionHolder holder; + holder.value = mode["value"]; + if (mode.contains("icon")) { + holder.icon = mode["icon"]; + } else [[unlikely]] { + SPDLOG_ERROR("Mode does not contain an icon key."); + } + if (mode.contains("label")) { + holder.label = mode["label"]; + } else [[unlikely]] { + SPDLOG_WARN("Thermostat entry for mode {} does not give a label. Will use raw mode value.", holder.value); + holder.label = mode; + } + this->_supported_swingh_modes.push_back(holder); + } else { + SPDLOG_ERROR("Mode does not contain a value key."); + } + } + + // No mode selected, default to first mode + if (!this->_supported_swingh_modes.empty() && this->_current_swingh_mode.value.empty()) { + SPDLOG_WARN("Thermostat {}::{} horizontal swing mode not set, will default to: {}.", this->_id, this->_name, this->_supported_swingh_modes[0].label); + this->_current_swingh_mode = this->_supported_swingh_modes[0]; + this->_requested_swingh_mode = this->_supported_swingh_modes[0]; + } + } +} + +void ThermostatEntity::set_mode(std::string mode) { + auto request_mode = std::find_if(this->_supported_modes.begin(), this->_supported_modes.end(), [&](const ThermostatOptionHolder &holder) { + return holder.value.compare(mode) == 0; + }); + if (request_mode != this->_supported_modes.end()) { + this->_requested_mode = *request_mode; + this->send_state_update_to_controller(); + } else { + SPDLOG_ERROR("Invalid mode ({}) for thermostat {}::{}.", mode, this->_id, this->_name); + } +} + +ThermostatOptionHolder ThermostatEntity::get_mode() { + return this->_requested_mode; +} + +std::vector ThermostatEntity::get_supported_modes() { + return this->_supported_modes; +} + +void ThermostatEntity::set_temperature(float temperature) { + this->_requested_temperature = temperature; + this->send_state_update_to_controller(); +} + +float ThermostatEntity::get_temperature() { + return this->_current_temperature; +} + +void ThermostatEntity::set_swing_mode(std::string swing_mode) { + auto request_swing_mode = std::find_if(this->_supported_swing_modes.begin(), this->_supported_swing_modes.end(), [&](const ThermostatOptionHolder &holder) { + return holder.value.compare(swing_mode) == 0; + }); + if (request_swing_mode != this->_supported_swing_modes.end()) { + this->_requested_swing_mode = *request_swing_mode; + this->send_state_update_to_controller(); + } else { + SPDLOG_ERROR("Invalid swing mode ({}) for thermostat {}::{}.", swing_mode, this->_id, this->_name); + } +} + +ThermostatOptionHolder ThermostatEntity::get_swing_mode() { + return this->_requested_swing_mode; +} + +std::vector ThermostatEntity::get_supported_swing_modes() { + return this->_supported_swing_modes; +} + +void ThermostatEntity::set_swing_horizontal_mode(std::string swingh_mode) { + auto request_swingh_mode = std::find_if(this->_supported_swingh_modes.begin(), this->_supported_swingh_modes.end(), [&](const ThermostatOptionHolder &holder) { + return holder.value.compare(swingh_mode) == 0; + }); + if (request_swingh_mode != this->_supported_swingh_modes.end()) { + this->_requested_swingh_mode = *request_swingh_mode; + this->send_state_update_to_controller(); + } else { + SPDLOG_ERROR("Invalid swing (horizontal) mode ({}) for thermostat {}::{}.", swingh_mode, this->_id, this->_name); + } +} + +ThermostatOptionHolder ThermostatEntity::get_swing_horizontal_mode() { + return this->_requested_swingh_mode; +} + +std::vector ThermostatEntity::get_supported_swing_horizontal_modes() { + return this->_supported_swingh_modes; +} + +void ThermostatEntity::send_state_update_to_nspanel() { + NSPanelEntityState state; + NSPanelEntityState_Thermostat *th_status = state.mutable_thermostat(); + th_status->set_thermostat_id(this->_id); + th_status->set_name(this->_name); + th_status->set_set_temperature(this->_current_temperature); + th_status->set_step_size(this->_step_size); + th_status->set_has_current_temperature(false); + + auto room = EntityManager::get_room(this->_room_id); + if (room && (*room)->has_temperature_sensor()) { + auto room_temp = (*room)->get_temperature(); + if (room_temp) { + th_status->set_current_temperature(*room_temp); + th_status->set_has_current_temperature(true); + } + } + + if (!this->_supported_modes.empty()) { + auto mode_options = th_status->add_options(); + mode_options->set_name("Mode"); + SPDLOG_DEBUG("Thermostat {}::{} current mode: {}, {}", this->_id, this->_name, this->_current_mode.label, this->_current_mode.value); + mode_options->set_current_value(this->_current_mode.label); + mode_options->set_current_icon(this->_current_mode.icon); + for (const auto &mode : this->_supported_modes) { + auto option = mode_options->add_options(); + option->set_value(mode.label); + option->set_icon(mode.icon); + } + } + + if (!this->_supported_fan_modes.empty()) { + auto fan_options = th_status->add_options(); + fan_options->set_name(ThermostatEntity::fan_label); + fan_options->set_current_value(this->_current_fan_mode.label); + fan_options->set_current_icon(this->_current_fan_mode.icon); + for (const auto &mode : this->_supported_fan_modes) { + auto option = fan_options->add_options(); + option->set_value(mode.label); + option->set_icon(mode.icon); + } + } + + if (!this->_supported_presets.empty()) { + auto preset_options = th_status->add_options(); + preset_options->set_name(ThermostatEntity::preset_label); + preset_options->set_current_value(this->_current_preset.label); + preset_options->set_current_icon(this->_current_preset.icon); + for (const auto &preset : this->_supported_presets) { + auto option = preset_options->add_options(); + option->set_value(preset.label); + option->set_icon(preset.icon); + } + } + + if (!this->_supported_swing_modes.empty()) { + auto swing_options = th_status->add_options(); + swing_options->set_name(ThermostatEntity::swing_label); + swing_options->set_current_value(this->_current_swing_mode.label); + swing_options->set_current_icon(this->_current_swing_mode.icon); + for (const auto &mode : this->_supported_swing_modes) { + auto option = swing_options->add_options(); + option->set_value(mode.label); + option->set_icon(mode.icon); + } + } + + if (!this->_supported_swingh_modes.empty()) { + auto swingh_options = th_status->add_options(); + swingh_options->set_name(ThermostatEntity::swingh_label); + swingh_options->set_current_value(this->_current_swingh_mode.label); + swingh_options->set_current_icon(this->_current_swingh_mode.icon); + for (const auto &mode : this->_supported_swingh_modes) { + auto option = swingh_options->add_options(); + option->set_value(mode.label); + option->set_icon(mode.icon); + } + } + + google::protobuf::util::MessageDifferencer differencer; + if (!differencer.Compare(this->_last_thermostat_state, state)) { + SPDLOG_DEBUG("Sending updated state for thermostat {}::{} over MQTT.", this->_id, this->_name); + this->_last_thermostat_state = state; + + MQTT_Manager::publish_protobuf(this->get_mqtt_state_topic(), state, true); + } else { + SPDLOG_DEBUG("Did not send state update for thermostat {}::{} as there were no changes.", this->_id, this->_name); + } +} + +void ThermostatEntity::set_fan_mode(std::string fan_mode) { + auto request_fan_mode = std::find_if(this->_supported_fan_modes.begin(), this->_supported_fan_modes.end(), [fan_mode](const ThermostatOptionHolder &holder) { + return holder.value.compare(fan_mode) == 0; + }); + if (request_fan_mode != this->_supported_fan_modes.end()) { + this->_requested_fan_mode = *request_fan_mode; + this->send_state_update_to_controller(); + } else { + SPDLOG_ERROR("Invalid fan mode ({}) for thermostat {}::{}. Will default to 'off'.", fan_mode, this->_id, this->_name); + } +} + +ThermostatOptionHolder ThermostatEntity::get_fan_mode() { + return this->_requested_fan_mode; +} + +std::vector ThermostatEntity::get_supported_fan_modes() { + return this->_supported_fan_modes; +} + +void ThermostatEntity::set_preset(std::string preset) { + auto request_preset = std::find_if(this->_supported_presets.begin(), this->_supported_presets.end(), [preset](const ThermostatOptionHolder &holder) { + return holder.value.compare(preset) == 0; + }); + if (request_preset != this->_supported_presets.end()) { + this->_requested_preset = *request_preset; + this->send_state_update_to_controller(); + } else { + SPDLOG_ERROR("Invalid preset ({}) for thermostat {}::{}. Will default to 'off'.", preset, this->_id, this->_name); + } +} + +ThermostatOptionHolder ThermostatEntity::get_preset() { + return this->_requested_preset; +} + +std::vector ThermostatEntity::get_supported_presets() { + return this->_supported_presets; +} + +MQTT_MANAGER_ENTITY_TYPE ThermostatEntity::get_type() { + return MQTT_MANAGER_ENTITY_TYPE::THERMOSTAT; +} + +MQTT_MANAGER_ENTITY_CONTROLLER ThermostatEntity::get_controller() { + return this->_controller; +} + +uint16_t ThermostatEntity::get_id() { + return this->_id; +} + +std::string ThermostatEntity::get_name() { + return this->_name; +} + +uint32_t ThermostatEntity::get_entity_page_id() { + return this->_entity_page_id; +} + +uint8_t ThermostatEntity::get_entity_page_slot() { + return this->_entity_page_slot; +} + +void ThermostatEntity::attach_delete_callback(void (*callback)(ThermostatEntity *)) { + this->_thermostat_destroyed_callbacks.connect(callback); +} + +void ThermostatEntity::detach_delete_callback(void (*callback)(ThermostatEntity *)) { + this->_thermostat_destroyed_callbacks.disconnect(callback); +} + +void ThermostatEntity::reset_requests() { + this->_requested_preset = this->_current_preset; + this->_requested_temperature = this->_current_temperature; + this->_requested_mode = this->_current_mode; + this->_requested_fan_mode = this->_current_fan_mode; + this->_requested_swing_mode = this->_current_swing_mode; +} + +void ThermostatEntity::command_callback(NSPanelMQTTManagerCommand &command) { + if (command.has_thermostat_command() && command.thermostat_command().thermostat_id() == this->_id) { + auto thermostat_command = command.thermostat_command(); + SPDLOG_DEBUG("Received command from NSPanel to set thermostat {}::{} setting {} to value {}", this->_id, this->_name, thermostat_command.option(), thermostat_command.new_value()); + + if (thermostat_command.option().compare(ThermostatEntity::mode_label) == 0) { + auto new_mode = std::find_if(this->_supported_modes.begin(), this->_supported_modes.end(), [thermostat_command](const ThermostatOptionHolder &mode) { + return mode.label.compare(thermostat_command.new_value()) == 0; + }); + if (new_mode != this->_supported_modes.end()) [[likely]] { + this->set_mode(new_mode->value); + } else { + SPDLOG_ERROR("Thermostat {}::{} received command to set set option {} to value {} but that value is not valid.", this->_id, this->_name, thermostat_command.option(), thermostat_command.new_value()); + } + } else if (thermostat_command.option().compare(ThermostatEntity::fan_label) == 0) { + auto new_mode = std::find_if(this->_supported_fan_modes.begin(), this->_supported_fan_modes.end(), [thermostat_command](const ThermostatOptionHolder &mode) { + return mode.label.compare(thermostat_command.new_value()) == 0; + }); + if (new_mode != this->_supported_fan_modes.end()) [[likely]] { + this->set_fan_mode(new_mode->value); + } else { + SPDLOG_ERROR("Thermostat {}::{} received command to set set option {} to value {} but that value is not valid.", this->_id, this->_name, thermostat_command.option(), thermostat_command.new_value()); + } + } else if (thermostat_command.option().compare(ThermostatEntity::preset_label) == 0) { + auto new_preset = std::find_if(this->_supported_presets.begin(), this->_supported_presets.end(), [thermostat_command](const ThermostatOptionHolder &mode) { + return mode.label.compare(thermostat_command.new_value()) == 0; + }); + if (new_preset != this->_supported_presets.end()) [[likely]] { + this->set_preset(new_preset->value); + } else { + SPDLOG_ERROR("Thermostat {}::{} received command to set set option {} to value {} but that value is not valid.", this->_id, this->_name, thermostat_command.option(), thermostat_command.new_value()); + } + } else if (thermostat_command.option().compare(ThermostatEntity::swing_label) == 0) { + auto new_mode = std::find_if(this->_supported_swing_modes.begin(), this->_supported_swing_modes.end(), [thermostat_command](const ThermostatOptionHolder &mode) { + return mode.label.compare(thermostat_command.new_value()) == 0; + }); + if (new_mode != this->_supported_swing_modes.end()) [[likely]] { + this->set_swing_mode(new_mode->value); + } else { + SPDLOG_ERROR("Thermostat {}::{} received command to set set option {} to value {} but that value is not valid.", this->_id, this->_name, thermostat_command.option(), thermostat_command.new_value()); + } + } else if (thermostat_command.option().compare(ThermostatEntity::swingh_label) == 0) { + auto new_mode = std::find_if(this->_supported_swingh_modes.begin(), this->_supported_swingh_modes.end(), [thermostat_command](const ThermostatOptionHolder &mode) { + return mode.label.compare(thermostat_command.new_value()) == 0; + }); + if (new_mode != this->_supported_swingh_modes.end()) [[likely]] { + this->set_swing_horizontal_mode(new_mode->value); + } else { + SPDLOG_ERROR("Thermostat {}::{} received command to set set option {} to value {} but that value is not valid.", this->_id, this->_name, thermostat_command.option(), thermostat_command.new_value()); + } + } + } else if (command.has_thermostat_temperature_command() && command.thermostat_temperature_command().thermostat_id() == this->_id) { + this->set_temperature(command.thermostat_temperature_command().temperature()); + } +} + +ThermostatEntity::~ThermostatEntity() { + SPDLOG_DEBUG("Destructor for thermostat {}::{} called.", this->_id, this->_name); + this->_thermostat_destroyed_callbacks(this); + this->_signal_entity_destroyed(); + CommandManager::detach_callback(boost::bind(&ThermostatEntity::command_callback, this, _1)); +} + +bool ThermostatEntity::can_toggle() { + return true; +} + +void ThermostatEntity::toggle() { +} + +std::string_view ThermostatEntity::get_icon() { + return EntityIcons::thermostat; +} + +uint16_t ThermostatEntity::get_icon_color() { + return GUI_Colors::icon_color_off; +} + +uint16_t ThermostatEntity::get_icon_active_color() { + return GUI_Colors::icon_color_off; +} + +std::string ThermostatEntity::get_mqtt_state_topic() { + std::string manager_address = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS); + return fmt::format("nspanel/mqttmanager_{}/entities/thermostats/{}/state", manager_address, this->get_id()); +} diff --git a/docker/MQTTManager/include/thermostat/thermostat.hpp b/docker/MQTTManager/include/thermostat/thermostat.hpp new file mode 100644 index 00000000..1ed2234f --- /dev/null +++ b/docker/MQTTManager/include/thermostat/thermostat.hpp @@ -0,0 +1,223 @@ +#ifndef MQTT_MANAGER_THERMOSTAT_HPP +#define MQTT_MANAGER_THERMOSTAT_HPP + +#include "protobuf_nspanel.pb.h" +#include +#include +#include +#include +#include +#include +#include +#include + +struct ThermostatOptionHolder { + std::string value = ""; + std::string icon; + std::string label; + + bool operator==(const ThermostatOptionHolder &other) const { + return value == other.value; + } +}; + +class ThermostatEntity : public MqttManagerEntity { +public: + ThermostatEntity(uint32_t thermostat_id); + + /** + * Update the config of the switch from DB. + */ + void reload_config(); + + /** + * Get the room ID of the switch. + */ + uint16_t get_room_id(); + + /** + * Set the thermostat mode + */ + void set_mode(std::string mode); + + /** + * Get the current mode of the thermostat. + */ + ThermostatOptionHolder get_mode(); + + /** + * Get all supported modes. + */ + std::vector get_supported_modes(); + + /** + * Set the thermostat temperature + */ + void set_temperature(float temperature); + + /** + * Get the current temperature of the thermostat. + */ + float get_temperature(); + + /** + * Set the thermostat fan mode + */ + void set_fan_mode(std::string fan_mode); + + /** + * Get the thermostat fan mode + */ + ThermostatOptionHolder get_fan_mode(); + + /** + * Get all supported fan modes. + */ + std::vector get_supported_fan_modes(); + + /** + * Set the thermostat preset + */ + void set_preset(std::string preset); + + /** + * Get the thermostat preset + */ + ThermostatOptionHolder get_preset(); + + /** + * Get all supported presets. + */ + std::vector get_supported_presets(); + + /** + * Set the thermostat swing mode + */ + void set_swing_mode(std::string swing_mode); + + /** + * Get the thermostat swing mode + */ + ThermostatOptionHolder get_swing_mode(); + + /** + * Get all supported swing modes. + */ + std::vector get_supported_swing_modes(); + + /** + * Set the thermostat swing horizontal mode + */ + void set_swing_horizontal_mode(std::string swing_mode); + + /** + * Get the thermostat swing mode + */ + ThermostatOptionHolder get_swing_horizontal_mode(); + + /** + * Get all supported swing modes. + */ + std::vector get_supported_swing_horizontal_modes(); + + /** + * Send state update to NSPanel via MQTT as Protobuf object. + */ + void send_state_update_to_nspanel(); + + /** + * Get the ID of the thermostat. + */ + uint16_t get_id(); + + /** + * Get the friendly name for the switch. + */ + std::string get_name(); + + /** + * Get the ID of the entity page this entity is placed on. + */ + uint32_t get_entity_page_id(); + + /** + * Get the slot in which this entity is placed on the given entity page. + */ + uint8_t get_entity_page_slot(); + + /** + * Attach a callback for when the thermostat is destroyed + */ + void attach_delete_callback(void (*callback)(ThermostatEntity *thermostat_entity)); + + /** + * Detach a callback for when the thermostat is destroyed + */ + void detach_delete_callback(void (*callback)(ThermostatEntity *thermostat_entity)); + + /** + * Compare requested and current values and change commands to controller to set the values as per request. + */ + virtual void send_state_update_to_controller() = 0; + + /** + * Set all requested values equal to current values. + */ + void reset_requests(); + + /** + * Callback for NSPanelMQTTManagerCommand protobuf received from MQTT + */ + void command_callback(NSPanelMQTTManagerCommand &command); + + MQTT_MANAGER_ENTITY_TYPE get_type(); + MQTT_MANAGER_ENTITY_CONTROLLER get_controller(); + bool can_toggle(); + void toggle(); + std::string_view get_icon(); + uint16_t get_icon_color(); + uint16_t get_icon_active_color(); + std::string get_mqtt_state_topic(); + + ~ThermostatEntity(); + +protected: + uint _id; + std::string _name; + uint16_t _room_id; + MQTT_MANAGER_ENTITY_CONTROLLER _controller; + uint32_t _entity_page_id; + uint8_t _entity_page_slot; + float _step_size; + + static constexpr const char *mode_label = "Mode"; + static constexpr const char *fan_label = "Fan"; + static constexpr const char *preset_label = "Presets"; + static constexpr const char *swing_label = "Swing"; + static constexpr const char *swingh_label = "H Swing"; + + std::vector _supported_modes; + std::vector _supported_swing_modes; + std::vector _supported_swingh_modes; + std::vector _supported_fan_modes; + std::vector _supported_presets; + + ThermostatOptionHolder _current_mode; + ThermostatOptionHolder _current_swing_mode; + ThermostatOptionHolder _current_swingh_mode; + ThermostatOptionHolder _current_fan_mode; + ThermostatOptionHolder _current_preset; + float _current_temperature = 0; + NSPanelEntityState _last_thermostat_state; + + ThermostatOptionHolder _requested_mode; + ThermostatOptionHolder _requested_swing_mode; + ThermostatOptionHolder _requested_swingh_mode; + ThermostatOptionHolder _requested_fan_mode; + ThermostatOptionHolder _requested_preset; + float _requested_temperature = 0; + + boost::signals2::signal _thermostat_destroyed_callbacks; +}; + +#endif // !MQTT_MANAGER_SWITCH diff --git a/docker/MQTTManager/include/weather/weather.cpp b/docker/MQTTManager/include/weather/weather.cpp index 2493210a..19c62981 100644 --- a/docker/MQTTManager/include/weather/weather.cpp +++ b/docker/MQTTManager/include/weather/weather.cpp @@ -44,24 +44,24 @@ void MQTTManagerWeather::_run_weather_thread() { void MQTTManagerWeather::reload_config() { std::lock_guard lock_guard(MQTTManagerWeather::_weater_data_mutex); - if (MqttManagerConfig::get_setting_with_default("outside_temp_provider", "").compare("home_assistant") == 0) { - OpenhabManager::detach_event_observer(MQTTManagerWeather::_outside_temperature_sensor_provider, &MQTTManagerWeather::openhab_temp_sensor_callback); - MQTTManagerWeather::_outside_temperature_sensor_entity_id = MqttManagerConfig::get_setting_with_default("outside_temp_sensor_entity_id", ""); + if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OUTSIDE_TEMP_SENSOR_PROVIDER).compare("home_assistant") == 0) { + OpenhabManager::detach_event_observer(MQTTManagerWeather::_outside_temperature_sensor_entity_id, &MQTTManagerWeather::openhab_temp_sensor_callback); + MQTTManagerWeather::_outside_temperature_sensor_entity_id = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OUTSIDE_TEMP_SENSOR_ENTITY_ID); HomeAssistantManager::attach_event_observer(MQTTManagerWeather::_outside_temperature_sensor_entity_id, &MQTTManagerWeather::home_assistant_event_callback); SPDLOG_INFO("Will load outside temperature from Home Assistant sensor {}", MQTTManagerWeather::_outside_temperature_sensor_entity_id); - } else if (MqttManagerConfig::get_setting_with_default("outside_temp_provider", "").compare("openhab") == 0) { + } else if (MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OUTSIDE_TEMP_SENSOR_PROVIDER).compare("openhab") == 0) { HomeAssistantManager::detach_event_observer(MQTTManagerWeather::_outside_temperature_sensor_entity_id, &MQTTManagerWeather::home_assistant_event_callback); - MQTTManagerWeather::_outside_temperature_sensor_entity_id = MqttManagerConfig::get_setting_with_default("outside_temp_sensor_entity_id", ""); + MQTTManagerWeather::_outside_temperature_sensor_entity_id = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::OUTSIDE_TEMP_SENSOR_ENTITY_ID); OpenhabManager::attach_event_observer(MQTTManagerWeather::_outside_temperature_sensor_entity_id, &MQTTManagerWeather::openhab_temp_sensor_callback); SPDLOG_INFO("Will load outside temperature from OpenHAB sensor {}", MQTTManagerWeather::_outside_temperature_sensor_entity_id); } - MQTTManagerWeather::_location_latitude = MqttManagerConfig::get_setting_with_default("location_latitude", ""); - MQTTManagerWeather::_location_longitude = MqttManagerConfig::get_setting_with_default("location_longitude", ""); - MQTTManagerWeather::_update_interval_minutes = std::stoi(MqttManagerConfig::get_setting_with_default("weather_update_interval", "10")); - MQTTManagerWeather::_wind_speed_format = MqttManagerConfig::get_setting_with_default("wind_speed_format", ""); - MQTTManagerWeather::_precipitation_unit = MqttManagerConfig::get_setting_with_default("precipitation_format", ""); - MQTTManagerWeather::_temperature_unit = MqttManagerConfig::get_setting_with_default("use_fahrenheit", "False").compare("True") == 0 ? "fahrenheit" : "celsius"; + MQTTManagerWeather::_location_latitude = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::LOCATION_LATITUDE); + MQTTManagerWeather::_location_longitude = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::LOCATION_LONGITUDE); + MQTTManagerWeather::_update_interval_minutes = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::WEATHER_UPDATE_INTERVAL); + MQTTManagerWeather::_wind_speed_format = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::WEATHER_WIND_SPEED_FORMAT); + MQTTManagerWeather::_precipitation_unit = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::WEATHER_PRECIPITATION_FORMAT); + MQTTManagerWeather::_temperature_unit = MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::USE_FAHRENHEIT) ? "fahrenheit" : "celsius"; } static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) { @@ -165,7 +165,7 @@ void MQTTManagerWeather::_process_weather_data(std::string &weather_string) { MQTTManagerWeather::_current_temperature = data["current"]["temperature_2m"]; } - if (MqttManagerConfig::get_settings().clock_24_hour_format) [[likely]] { + if (!MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::CLOCK_US_STYLE)) [[likely]] { MQTTManagerWeather::_next_sunrise = fmt::format("{:%H:%M}", MQTTManagerWeather::_forecast_weather_info[0].sunrise); MQTTManagerWeather::_next_sunrise_hour = MQTTManagerWeather::_forecast_weather_info[0].sunrise.tm_hour; MQTTManagerWeather::_next_sunset = fmt::format("{:%H:%M}", MQTTManagerWeather::_forecast_weather_info[0].sunset); @@ -272,7 +272,7 @@ void MQTTManagerWeather::send_state_update() { std::string new_weather_data; if (weather_protbuf.SerializeToString(&new_weather_data)) { if (new_weather_data.compare(MQTTManagerWeather::_last_weather_update) != 0) { - std::string weather_update_topic = fmt::format("nspanel/mqttmanager_{}/status/weather", MqttManagerConfig::get_settings().manager_address); + std::string weather_update_topic = fmt::format("nspanel/mqttmanager_{}/status/weather", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)); SPDLOG_DEBUG("Sending new weather data out on topic {}.", weather_update_topic); MQTT_Manager::publish(weather_update_topic, new_weather_data, true); MQTTManagerWeather::_last_weather_update = new_weather_data; diff --git a/docker/MQTTManager/include/websocket_server/websocket_server.cpp b/docker/MQTTManager/include/websocket_server/websocket_server.cpp index cb6ab0b3..94fc3448 100644 --- a/docker/MQTTManager/include/websocket_server/websocket_server.cpp +++ b/docker/MQTTManager/include/websocket_server/websocket_server.cpp @@ -1,15 +1,19 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include +#include +#include #include #include #include @@ -244,13 +248,16 @@ void WebsocketServer::_websocket_message_callback(std::shared_ptr lock_guard_callbacks(WebsocketServer::_on_stomp_send_message_callbacks_mutex); - if (WebsocketServer::_on_stomp_send_message_callbacks.count(frame->headers["destination"]) > 0) { + WebsocketServer::_on_global_stomp_send_message_callbacks(*frame); + if (WebsocketServer::_on_stomp_send_message_callbacks.count(frame->headers["destination"]) > 0) [[likely]] { SPDLOG_DEBUG("Received STOMP SEND command for existing topic, setting topic '{}' to value '{}'", frame->headers["destination"], frame->body); WebsocketServer::_on_stomp_send_message_callbacks[frame->headers["destination"]](frame.value()); return; + } else { + if (!boost::algorithm::starts_with(frame->headers["destination"], "mqtt/")) { // If the string starts with "mqtt/" it was ment for the global callback. Do not warn in case an attached callback does not exist. + SPDLOG_WARN("Received STOMP SEND command for unknown topic. Will not publish, topic '{}', value '{}'", frame->headers["destination"], frame->body); + } } - - SPDLOG_WARN("Received STOMP SEND command for unknown topic. Will not publish, topic '{}', value '{}'", frame->headers["destination"], frame->body); } else { SPDLOG_WARN("Received unknown STOMP frame type {}.", static_cast(frame->type)); } @@ -271,7 +278,6 @@ void WebsocketServer::_websocket_message_callback(std::shared_ptr lock_guard(WebsocketServer::_server_mutex); - SPDLOG_DEBUG("Updating stomp topic value '{}'", topic_name); for (auto &topic : WebsocketServer::_stomp_topics) { if (topic->get_name().compare(topic_name) == 0) { topic->update_value(value); @@ -282,7 +288,6 @@ void WebsocketServer::update_stomp_topic_value(std::string topic_name, std::stri void WebsocketServer::update_stomp_topic_value(std::string topic_name, nlohmann::json &value) { std::lock_guard lock_guard(WebsocketServer::_server_mutex); - SPDLOG_DEBUG("Updating stomp topic JSON '{}'", topic_name); for (auto &topic : WebsocketServer::_stomp_topics) { if (topic->get_name().compare(topic_name) == 0) { topic->update_value(value); @@ -422,7 +427,7 @@ void WebsocketServer::send_stomp_frame(StompFrame &frame, ix::WebSocket &websock } // TODO: Make content-type adjustable - frame.headers["content-type"] = "text/plain;charset=utf-8"; + frame.headers["content-type"] = "text/plain"; for (auto &header : frame.headers) { message.append(header.first); @@ -436,7 +441,11 @@ void WebsocketServer::send_stomp_frame(StompFrame &frame, ix::WebSocket &websock message.push_back('\0'); message.push_back('\n'); - websocket.send(message); + try { + websocket.send(message); + } catch (std::exception &ex) { + SPDLOG_ERROR("Failed to send message over websocket. Error: {}", ex.what()); + } } void WebsocketServer::attach_message_callback(std::function callback) { diff --git a/docker/MQTTManager/include/websocket_server/websocket_server.hpp b/docker/MQTTManager/include/websocket_server/websocket_server.hpp index fc88abfd..c0442f0b 100644 --- a/docker/MQTTManager/include/websocket_server/websocket_server.hpp +++ b/docker/MQTTManager/include/websocket_server/websocket_server.hpp @@ -92,6 +92,13 @@ class WebsocketServer { */ static void send_stomp_frame(StompFrame &frame, ix::WebSocket &websocket); + template + static void attach_stomp_global_callback(CALLBACK_BIND callback) { + std::lock_guard mutex_guard(WebsocketServer::_on_stomp_send_message_callbacks_mutex); + WebsocketServer::_on_global_stomp_send_message_callbacks.disconnect(callback); // First disconnect in case it was already connected to avaid duplicate callbacks + WebsocketServer::_on_global_stomp_send_message_callbacks.connect(callback); + } + /** * Attach a callback to be called when a message is received on a specific topic using STOMP. */ @@ -102,6 +109,12 @@ class WebsocketServer { WebsocketServer::_on_stomp_send_message_callbacks[topic].connect(callback); } + template + static void detach_global_stomp_callback(CALLBACK_BIND callback) { + std::lock_guard mutex_guard(WebsocketServer::_on_stomp_send_message_callbacks_mutex); + WebsocketServer::_on_global_stomp_send_message_callbacks.disconnect(callback); + } + template static void detach_stomp_callback(std::string topic, CALLBACK_BIND callback) { std::lock_guard mutex_guard(WebsocketServer::_on_stomp_send_message_callbacks_mutex); @@ -153,6 +166,7 @@ class WebsocketServer { // Callback for when a SEND message is received on a STOMP topic static inline std::mutex _on_stomp_send_message_callbacks_mutex; static inline boost::ptr_map> _on_stomp_send_message_callbacks; + static inline boost::signals2::signal _on_global_stomp_send_message_callbacks; static inline std::mutex _active_warnings_mutex; static inline std::list _active_warnings; diff --git a/docker/MQTTManager/src/main.cpp b/docker/MQTTManager/src/main.cpp index 464bc0e0..18ad27bb 100644 --- a/docker/MQTTManager/src/main.cpp +++ b/docker/MQTTManager/src/main.cpp @@ -25,12 +25,15 @@ #include #include #include +#include #include #include +#if defined(TEST_MODE) && TEST_MODE == 1 +#include +#endif + #define SIGUSR1 10 -std::string last_time_published; -std::string last_date_published; void sigusr1_handler(int signal) { if (signal == SIGUSR1) { @@ -62,10 +65,10 @@ void publish_time_and_date() { std::string date_str; std::time_t time = std::time({}); - std::strftime(date_buffer, 100, MqttManagerConfig::get_settings().date_format.c_str(), std::localtime(&time)); + std::strftime(date_buffer, 100, MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::DATE_FORMAT).c_str(), std::localtime(&time)); date_str = date_buffer; - if (MqttManagerConfig::get_settings().clock_24_hour_format) { + if (!MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::CLOCK_US_STYLE)) [[likely]] { std::strftime(time_buffer, 20, "%H:%M", std::localtime(&time)); time_str = time_buffer; } else { @@ -74,26 +77,54 @@ void publish_time_and_date() { time_str = time_buffer; } - if (time_str.compare(last_time_published) != 0) { - MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/time", MqttManagerConfig::get_settings().manager_address), time_buffer, true); - if (MqttManagerConfig::get_settings().clock_24_hour_format) { - MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/ampm", MqttManagerConfig::get_settings().manager_address), "", true); - } else { - MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/ampm", MqttManagerConfig::get_settings().manager_address), ampm_buffer, true); - } - last_time_published = time_buffer; + MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/time", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)), time_buffer, true); + if (!MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::CLOCK_US_STYLE)) [[likely]] { + MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/ampm", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)), "", true); + } else { + MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/ampm", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)), ampm_buffer, true); } - if (date_str.compare(last_date_published) != 0) { - MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/date", MqttManagerConfig::get_settings().manager_address), date_buffer, true); - last_date_published = date_buffer; + MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/date", MqttManagerConfig::get_setting_with_default(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)), date_buffer, true); + + // Sleep until next minute + auto t_now = std::chrono::system_clock::now(); + auto t_now_c = std::chrono::system_clock::to_time_t(t_now); + std::tm next_minute_tm = *std::localtime(&t_now_c); + next_minute_tm.tm_sec = 1; // Always wait to minute change over and not the exact same second + + next_minute_tm.tm_min++; + if (next_minute_tm.tm_min >= 60) { // We went over to next hour + next_minute_tm.tm_min = 0; + next_minute_tm.tm_hour++; + if (next_minute_tm.tm_hour >= 24) { // We went over to next day + next_minute_tm.tm_hour = 0; + + // Handle day change + if (next_minute_tm.tm_mday >= 32) { // We went over to next month + next_minute_tm.tm_mday = 1; + next_minute_tm.tm_mon++; + if (next_minute_tm.tm_mon >= 12) { // We went over to next year + next_minute_tm.tm_mon = 0; + next_minute_tm.tm_year++; + } + } + } } - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + auto t_next_minute = std::chrono::system_clock::from_time_t(std::mktime(&next_minute_tm)); + std::this_thread::sleep_until(t_next_minute); } } -int main(void) { +int main(int argc, char *argv[]) { +// If we are in test mode, don't start the MQTTManager. Simply run all test then exit. +#if defined(TEST_MODE) && TEST_MODE == 1 + database_manager::init(); + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +#endif + SPDLOG_INFO("Starting MQTTManager."); std::filesystem::path log_partition_path = "/dev/shm/"; diff --git a/docker/docker-build_and_run.sh b/docker/docker-build_and_run.sh index 20f71851..4cd9b547 100755 --- a/docker/docker-build_and_run.sh +++ b/docker/docker-build_and_run.sh @@ -21,4 +21,5 @@ if [ ! -e "data/secret.key" ] && [ -e "$(pwd)/web/nspanelmanager/secret.key" ]; cp "$(pwd)/web/nspanelmanager/secret.key" "data/secret.key" fi -docker build -t nspanelmanager . && docker run --name nspanelmanager -v /etc/timezone:/etc/timezone:ro -v "$(pwd)/data/":"/data/" -d -p 8000:8000 -p 8001:8001 nspanelmanager +#docker build -t nspanelmanager . && docker run --name nspanelmanager -v /etc/timezone:/etc/timezone:ro -v "$(pwd)/data/":"/data/" -d -p 8000:8000 -p 8001:8001 nspanelmanager +docker build -t nspanelmanager . && docker run --name nspanelmanager -v /etc/localtime:/etc/localtime:ro -v "$(pwd)/data/":"/data/" -d -p 8000:8000 -p 8001:8001 nspanelmanager diff --git a/docker/docker-build_and_run_dev.sh b/docker/docker-build_and_run_dev.sh index 809caa0d..044301fc 100755 --- a/docker/docker-build_and_run_dev.sh +++ b/docker/docker-build_and_run_dev.sh @@ -32,6 +32,7 @@ else docker buildx build --platform "$TARGETPLATFORM" --build-arg no_mqttmanager_build=yes --build-arg IS_DEVEL=yes -t nspanelmanager . fi if [ "$?" == 0 ]; then - docker run --rm --name nspanelmanager --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --mac-address 02:42:ac:11:ff:ff -it -v /etc/timezone:/etc/timezone:ro -v "$(pwd)/web":/usr/src/app/ -v "$(pwd)/data":/data/ -v "$(pwd)/MQTTManager/":/MQTTManager/ -v "$(pwd)/nginx/sites-templates/":/etc/nginx/sites-templates/ -v "$(pwd)/nginx/sites-enabled/":/etc/nginx/sites-enabled/ -v "$(pwd)/HMI_files/":/usr/src/app/nspanelmanager/HMI_files/ -v "$(pwd)/../":/full_git/ -p 8000:8000 nspanelmanager /bin/bash + #docker run --rm --name nspanelmanager --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --mac-address 02:42:ac:11:ff:ff -it -v /etc/timezone:/etc/timezone:ro -v "$(pwd)/web":/usr/src/app/ -v "$(pwd)/data":/data/ -v "$(pwd)/MQTTManager/":/MQTTManager/ -v "$(pwd)/nginx/sites-templates/":/etc/nginx/sites-templates/ -v "$(pwd)/nginx/sites-enabled/":/etc/nginx/sites-enabled/ -v "$(pwd)/HMI_files/":/usr/src/app/nspanelmanager/HMI_files/ -v "$(pwd)/../":/full_git/ -p 8000:8000 nspanelmanager /bin/bash + docker run --rm --name nspanelmanager --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --mac-address 02:42:ac:11:ff:ff -it -v /etc/localtime:/etc/localtime:ro -v "$(pwd)/web":/usr/src/app/ -v "$(pwd)/data":/data/ -v "$(pwd)/MQTTManager/":/MQTTManager/ -v "$(pwd)/nginx/sites-templates/":/etc/nginx/sites-templates/ -v "$(pwd)/nginx/sites-enabled/":/etc/nginx/sites-enabled/ -v "$(pwd)/HMI_files/":/usr/src/app/nspanelmanager/HMI_files/ -v "$(pwd)/../":/full_git/ -p 8000:8000 nspanelmanager /bin/bash docker rmi nspanelmanager fi diff --git a/docker/nginx/sites-enabled/nspanelmanager.conf b/docker/nginx/sites-enabled/nspanelmanager.conf index 8f185535..596a9d46 100644 --- a/docker/nginx/sites-enabled/nspanelmanager.conf +++ b/docker/nginx/sites-enabled/nspanelmanager.conf @@ -14,6 +14,11 @@ upstream mqttmanager { server 127.0.0.1:8002; } +upstream nextion_img { + server 127.0.0.1:8003; +} + + # configuration of the server server { # the port your site will be served on @@ -36,6 +41,10 @@ server { proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host;} + location /nextion-img { + proxy_pass http://nextion_img; + } + # Finally, send all non-media requests to the Django server. location / { uwsgi_pass django; diff --git a/docker/nginx/sites-templates/nspanelmanager_devel.template b/docker/nginx/sites-templates/nspanelmanager_devel.template index c8e7be91..23f07aeb 100644 --- a/docker/nginx/sites-templates/nspanelmanager_devel.template +++ b/docker/nginx/sites-templates/nspanelmanager_devel.template @@ -14,6 +14,11 @@ upstream mqttmanager { server 127.0.0.1:8002; } +upstream nextion_img { + server 127.0.0.1:8003; +} + + # configuration of the server server { # the port your site will be served on @@ -32,6 +37,10 @@ server { proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host;} + location /nextion-img { + proxy_pass http://nextion_img; + } + location /{ proxy_pass http://django; diff --git a/docker/nginx/sites-templates/nspanelmanager_prod.template b/docker/nginx/sites-templates/nspanelmanager_prod.template index 8f185535..596a9d46 100644 --- a/docker/nginx/sites-templates/nspanelmanager_prod.template +++ b/docker/nginx/sites-templates/nspanelmanager_prod.template @@ -14,6 +14,11 @@ upstream mqttmanager { server 127.0.0.1:8002; } +upstream nextion_img { + server 127.0.0.1:8003; +} + + # configuration of the server server { # the port your site will be served on @@ -36,6 +41,10 @@ server { proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host;} + location /nextion-img { + proxy_pass http://nextion_img; + } + # Finally, send all non-media requests to the Django server. location / { uwsgi_pass django; diff --git a/docker/protobuf/compile_all.sh b/docker/protobuf/compile_all.sh index afee26b6..3d49134c 100755 --- a/docker/protobuf/compile_all.sh +++ b/docker/protobuf/compile_all.sh @@ -1,7 +1,7 @@ #!/bin/bash DEST_DIR_MQTTMANAGER=/MQTTManager/include/protobuf/ DEST_DIR_DJANGO=/usr/src/app/nspanelmanager/web/protobuf/ -DEST_DIR_FIRMWARE=/full_git/firmware/firmware_espidf/lib/ProtoBuf/ +DEST_DIR_FIRMWARE=/full_git/protobuf_firmware/ SRC_FILES=('protobuf_mqttmanager.proto' 'protobuf_general.proto' 'protobuf_formats.proto') for SRC_FILE in ${SRC_FILES[@]}; do diff --git a/docker/protobuf/protobuf_nspanel.proto b/docker/protobuf/protobuf_nspanel.proto index 15c60b27..119107e6 100644 --- a/docker/protobuf/protobuf_nspanel.proto +++ b/docker/protobuf/protobuf_nspanel.proto @@ -3,240 +3,264 @@ syntax = "proto3"; // Protobufs defined in this .proto file are used to communicate with the MQTTManager message NSPanelConfig { - string name = 1; - int32 default_room = 2; - enum NSPanelDefaultPage { - HOME = 0; - SCENES = 1; - ENTITIES = 2; - }; - NSPanelDefaultPage default_page = 3; - int32 screensaver_activation_timeout = 4; - int32 min_button_push_time = 5; - int32 button_long_press_time = 6; - int32 special_mode_trigger_time = 7; - int32 special_mode_release_time = 8; - int32 screen_dim_level = 9; - int32 screensaver_dim_level = 10; - - // Make sure this is updated together with protobuf_mqttmanager - enum NSPanelScreensaverMode { - WEATHER_WITH_BACKGROUND = 0; - WEATHER_WITHOUT_BACKGROUND = 1; - DATETIME_WITH_BACKGROUND = 3; - DATETIME_WITHOUT_BACKGROUND = 4; - NO_SCREENSAVER = 5; - } - NSPanelScreensaverMode screensaver_mode = 11; - bool show_screensaver_inside_temperature = 12; - bool show_screensaver_outside_temperature = 13; - bool clock_us_style = 14; - bool use_fahrenheit = 15; - bool is_us_panel = 16; - - message RoomInfo { - int32 room_id = 1; - repeated int32 entity_page_ids = 2; - repeated int32 scene_page_ids = 3; - } - repeated RoomInfo room_infos = 17; // Allowed rooms - - bool reverse_relays = 18; - bool relay1_default_mode = 19; - bool relay2_default_mode = 20; - int32 temperature_calibration = 21; - - // There are more button modes avaiable but those are handled by the manager and no the panel itself. - // Only send modes that the panel itself can handle or simply tell the panel to tell the manager. - enum NSPanelButtonMode { - DIRECT = 0; - FOLLOW = 1; - NOTIFY_MANAGER = 2; - }; - NSPanelButtonMode button1_mode = 22; - NSPanelButtonMode button2_mode = 25; - - repeated int32 global_scene_entity_page_ids = 30; - bool optimistic_mode = 31; - int32 raise_light_level_to_100_above = 32; - int32 nspanel_id = 33; - - repeated int32 relay1_relay_group = 35; - repeated int32 relay2_relay_group = 37; - - int32 default_light_brightess = 38; // Default brightness for lights when trying to turn on but the current brightness is 0 - - bool locked_to_default_room = 39; - - string inside_temperature_sensor_mqtt_topic = 40; // The topic for an inside temperature sensor. If this is empty then no sensor is configured. + string name = 1; + int32 default_room = 2; + enum NSPanelDefaultPage { + HOME = 0; + SCENES = 1; + ENTITIES = 2; + } + NSPanelDefaultPage default_page = 3; + int32 screensaver_activation_timeout = 4; + int32 min_button_push_time = 5; + int32 button_long_press_time = 6; + int32 special_mode_trigger_time = 7; + int32 special_mode_release_time = 8; + int32 screen_dim_level = 9; + int32 screensaver_dim_level = 10; + + // Make sure this is updated together with protobuf_mqttmanager + enum NSPanelScreensaverMode { + WEATHER_WITH_BACKGROUND = 0; + WEATHER_WITHOUT_BACKGROUND = 1; + DATETIME_WITH_BACKGROUND = 3; + DATETIME_WITHOUT_BACKGROUND = 4; + NO_SCREENSAVER = 5; + } + NSPanelScreensaverMode screensaver_mode = 11; + bool show_screensaver_inside_temperature = 12; + bool show_screensaver_outside_temperature = 13; + bool clock_us_style = 14; + bool use_fahrenheit = 15; + bool is_us_panel = 16; + + message RoomInfo { + int32 room_id = 1; + repeated int32 entity_page_ids = 2; + repeated int32 scene_page_ids = 3; + } + repeated RoomInfo room_infos = 17; // Allowed rooms + + bool reverse_relays = 18; + bool relay1_default_mode = 19; + bool relay2_default_mode = 20; + int32 temperature_calibration = 21; + + // There are more button modes avaiable but those are handled by the manager and no the panel itself. + // Only send modes that the panel itself can handle or simply tell the panel to tell the manager. + enum NSPanelButtonMode { + DIRECT = 0; + FOLLOW = 1; + NOTIFY_MANAGER = 2; + THERMOSTAT_HEAT = 3; + THERMOSTAT_COOL = 4; + } + NSPanelButtonMode button1_mode = 22; + NSPanelButtonMode button2_mode = 25; + + repeated int32 global_scene_entity_page_ids = 30; + bool optimistic_mode = 31; + int32 raise_light_level_to_100_above = 32; + int32 nspanel_id = 33; + + repeated int32 relay1_relay_group = 35; + repeated int32 relay2_relay_group = 37; + + int32 default_light_brightess = 38; // Default brightness for lights when trying to turn on but the current brightness is 0 + + bool locked_to_default_room = 39; + + string inside_temperature_sensor_mqtt_topic = 40; // The topic for an inside temperature sensor. If this is empty then no sensor is configured. + + int32 button1_lower_temperature = 41; // At what temperature should relay1 turn on (heating) or off (cooling) when in thermostat mode. + int32 button1_upper_temperature = 42; // At what temperature should relay1 turn off (heating) or on (cooling) when in thermostat mode. + int32 button2_lower_temperature = 43; // At what temperature should relay2 turn on (heating) or off (cooling) when in thermostat mode. + int32 button2_upper_temperature = 44; // At what temperature should relay2 turn off (heating) or on (cooling) when in thermostat mode. } enum NSPanelWarningLevel { - CRITICAL = 0; - ERROR =1; - WARNING = 2; - INFO = 3; - DEBUG = 4; - TRACE = 5; + CRITICAL = 0; + ERROR = 1; + WARNING = 2; + INFO = 3; + DEBUG = 4; + TRACE = 5; } message NSPanelWarning { - NSPanelWarningLevel level = 1; - string text = 2; + NSPanelWarningLevel level = 1; + string text = 2; } message NSPanelStatusReport { - enum state { - ONLINE = 0; - OFFLINE = 1; - UPDATING_TFT = 2; - UPDATING_FIRMWARE = 3; - UPDATING_LITTLEFS = 4; - } - state nspanel_state = 1; - int32 update_progress = 2; - int32 rssi = 3; - int32 heap_used_pct = 4; - string mac_address = 5; - float temperature = 6; - string ip_address = 7; - - repeated NSPanelWarning warnings = 8; - - string md5_firmware = 9; - string md5_littlefs = 10; - string md5_tft_gui = 11; + enum state { + ONLINE = 0; + OFFLINE = 1; + UPDATING_TFT = 2; + UPDATING_FIRMWARE = 3; + UPDATING_LITTLEFS = 4; + } + state nspanel_state = 1; + int32 update_progress = 2; + int32 rssi = 3; + int32 heap_used_pct = 4; + string mac_address = 5; + float temperature = 6; + string ip_address = 7; + + repeated NSPanelWarning warnings = 8; + + string md5_firmware = 9; + string md5_littlefs = 10; + string md5_tft_gui = 11; + + bool has_humidity = 12; + float humidity = 13; + bool has_pressure = 14; + float pressure = 15; } message NSPanelLightStatus { - int32 id = 1; - string name = 2; - bool can_dim = 3; - bool can_color_temperature = 4; - bool can_rgb = 5; - int32 light_level = 6; - int32 color_temp = 7; - int32 hue = 8; - int32 saturation = 9; - int32 room_view_position = 10; + int32 id = 1; + string name = 2; + bool can_dim = 3; + bool can_color_temperature = 4; + bool can_rgb = 5; + int32 light_level = 6; + int32 color_temp = 7; + int32 hue = 8; + int32 saturation = 9; + int32 room_view_position = 10; } // Container of data shown on the "Entities page". // When pressing the next or previous button on the panel it will request a new NSPanelRoomEnititesPage from the manager. message NSPanelRoomEntitiesPage { - int32 id = 1; - int32 page_type = 2; // 12, 8 or 4 entities shown? - string header_text = 3; - - message EntitySlot { - int32 room_view_position = 1; - string name = 2; - string icon = 3; - int32 pco = 4; - int32 pco2 = 5; - bool can_save_scene = 6; - string mqtt_state_topic = 7; // MQTT topic where NSPanelEntityState is sent for control of individual entity. Empty if not controllable individually. - } - - repeated EntitySlot entities = 4; + int32 id = 1; + int32 page_type = 2; // 12, 8 or 4 entities shown? + string header_text = 3; + + message EntitySlot { + int32 room_view_position = 1; + string name = 2; + string icon = 3; + int32 pco = 4; + int32 pco2 = 5; + bool can_save_scene = 6; + string mqtt_state_topic = 7; // MQTT topic where NSPanelEntityState is sent for control of individual entity. Empty if not controllable individually. + } + + repeated EntitySlot entities = 4; } // Data showed on the main page. // When pressing "next room" it will request a new NSPanelRoomStatus from the manager. message NSPanelRoomStatus { - int32 id = 1; - string name = 2; - int32 average_dim_level = 3; - int32 ceiling_lights_dim_level = 4; - int32 table_lights_dim_level = 5; - int32 average_color_temperature = 6; - int32 ceiling_lights_color_temperature_value = 7; - int32 table_lights_color_temperature_value = 8; - int32 num_ceiling_lights = 9; - int32 num_table_lights = 10; - int32 num_ceiling_lights_on = 11; - int32 num_table_lights_on = 12; - - repeated int32 entity_page_ids = 13; + int32 id = 1; + string name = 2; + int32 average_dim_level = 3; + int32 ceiling_lights_dim_level = 4; + int32 table_lights_dim_level = 5; + int32 average_color_temperature = 6; + int32 ceiling_lights_color_temperature_value = 7; + int32 table_lights_color_temperature_value = 8; + int32 num_ceiling_lights = 9; + int32 num_table_lights = 10; + int32 num_ceiling_lights_on = 11; + int32 num_table_lights_on = 12; + + repeated int32 entity_page_ids = 13; } - message NSPanelWeatherUpdate { - message ForecastItem { - string weather_icon = 1; - string precipitation_string = 2; - string temperature_maxmin_string = 3; - string wind_string = 4; - string display_string = 5; - } - repeated ForecastItem forecast_items = 1; - string current_weather_icon = 2; - string current_temperature_string = 3; - string current_maxmin_temperature = 4; - string current_wind_string = 5; - string sunrise_string = 6; - string sunset_string = 7; - string current_precipitation_string = 8; + message ForecastItem { + string weather_icon = 1; + string precipitation_string = 2; + string temperature_maxmin_string = 3; + string wind_string = 4; + string display_string = 5; + } + repeated ForecastItem forecast_items = 1; + string current_weather_icon = 2; + string current_temperature_string = 3; + string current_maxmin_temperature = 4; + string current_wind_string = 5; + string sunrise_string = 6; + string sunset_string = 7; + string current_precipitation_string = 8; } // Command send from NSPanel to MQTTManager message NSPanelMQTTManagerCommand { - enum AffectLightsOptions { - ALL = 0; - TABLE_LIGHTS = 1; - CEILING_LIGHTS = 2; - } - message FirstPageTurnLightOn { - AffectLightsOptions affect_lights = 1; - int32 brightness_slider_value = 2; - int32 kelvin_slider_value = 3; - int32 selected_room = 4; - bool global = 5; - bool has_brightness_value = 6; - bool has_kelvin_value = 7; - } - message FirstPageTurnLightOff { - AffectLightsOptions affect_lights = 1; - bool global = 2; - } - - // TODO: Once protobuf-c-compiler gets updated, perhaps it's possible to build protobuf C files - // with optional arguments. - message LightCommand { - repeated int32 light_ids = 1; - bool has_brightness = 2; - int32 brightness = 3; - bool has_color_temperature = 4; - int32 color_temperature = 5; - bool has_hue = 6; - int32 hue = 7; - bool has_saturation = 8; - int32 saturation = 9; - } - - message ToggleEntityFromEntitiesPage { - int32 entity_page_id = 1; - int32 entity_slot = 2; - } - - message SaveSceneCommand { - int32 entity_page_id = 1; - int32 entity_slot = 2; - } - - message ButtonPressed { - int32 button_id = 2; - } - - oneof CommandData { - FirstPageTurnLightOn first_page_turn_on = 1; - FirstPageTurnLightOff first_page_turn_off = 2; - LightCommand light_command = 3; - ToggleEntityFromEntitiesPage toggle_entity_from_entities_page = 4; - SaveSceneCommand save_scene_command = 5; - ButtonPressed button_pressed = 6; - } - - - int32 nspanel_id = 100; + enum AffectLightsOptions { + ALL = 0; + TABLE_LIGHTS = 1; + CEILING_LIGHTS = 2; + } + message FirstPageTurnLightOn { + AffectLightsOptions affect_lights = 1; + int32 brightness_slider_value = 2; + int32 kelvin_slider_value = 3; + int32 selected_room = 4; + bool global = 5; + bool has_brightness_value = 6; + bool has_kelvin_value = 7; + } + message FirstPageTurnLightOff { + AffectLightsOptions affect_lights = 1; + bool global = 2; + } + + // TODO: Once protobuf-c-compiler gets updated, perhaps it's possible to build protobuf C files + // with optional arguments. + message LightCommand { + repeated int32 light_ids = 1; + bool has_brightness = 2; + int32 brightness = 3; // Brightness in 0-100% + bool has_color_temperature = 4; + int32 color_temperature = 5; // Color temperature in 0-100% + bool has_hue = 6; + int32 hue = 7; + bool has_saturation = 8; + int32 saturation = 9; + } + + message ToggleEntityFromEntitiesPage { + int32 entity_page_id = 1; + int32 entity_slot = 2; + } + + message SaveSceneCommand { + int32 entity_page_id = 1; + int32 entity_slot = 2; + } + + message ButtonPressed { + int32 button_id = 2; + } + + message ThermostatTemperatureCommand { + int32 thermostat_id = 1; + float temperature = 2; + } + + message ThermostatCommand { + int32 thermostat_id = 1; + string option = 2; + string new_value = 3; + } + + oneof CommandData { + FirstPageTurnLightOn first_page_turn_on = 1; + FirstPageTurnLightOff first_page_turn_off = 2; + LightCommand light_command = 3; + ToggleEntityFromEntitiesPage toggle_entity_from_entities_page = 4; + SaveSceneCommand save_scene_command = 5; + ButtonPressed button_pressed = 6; + + ThermostatTemperatureCommand thermostat_temperature_command = 7; + ThermostatCommand thermostat_command = 8; + } + + int32 nspanel_id = 100; } diff --git a/docker/protobuf/protobuf_nspanel_entity.proto b/docker/protobuf/protobuf_nspanel_entity.proto index 29824942..e76ff7f7 100644 --- a/docker/protobuf/protobuf_nspanel_entity.proto +++ b/docker/protobuf/protobuf_nspanel_entity.proto @@ -22,7 +22,30 @@ message NSPanelEntityState { LightMode current_light_mode = 9; } + message Thermostat { + int32 thermostat_id = 1; + string name = 2; + float current_temperature = 3; + bool has_current_temperature = 4; + float set_temperature = 5; + float step_size = 6; + + message ThermostatOption { + string name = 1; + string current_value = 2; + string current_icon = 3; + message ThermostatOptionValue { + string value = 1; + string icon = 2; + } + + repeated ThermostatOptionValue options = 4; + } + repeated ThermostatOption options = 7; + } + oneof entity { Light light = 1; + Thermostat thermostat = 2; } } diff --git a/docker/ruff.toml b/docker/ruff.toml new file mode 100644 index 00000000..b0e205b5 --- /dev/null +++ b/docker/ruff.toml @@ -0,0 +1,10 @@ +# Allow lines to be as long as 120. +line-length = 320 + +[lint] +# Avoid enforcing line-length violations (`E501`) +ignore = ["E501"] + +[format] +# Use double quotes when formatting. +quote-style = "double" diff --git a/docker/web/nspanelmanager/firmware.bin b/docker/web/nspanelmanager/firmware.bin deleted file mode 100644 index 7bfd71f9..00000000 Binary files a/docker/web/nspanelmanager/firmware.bin and /dev/null differ diff --git a/docker/web/nspanelmanager/data_file.bin b/docker/web/nspanelmanager/firmware/custom/data_file.bin similarity index 99% rename from docker/web/nspanelmanager/data_file.bin rename to docker/web/nspanelmanager/firmware/custom/data_file.bin index 2387fb9e..3ebb6055 100644 Binary files a/docker/web/nspanelmanager/data_file.bin and b/docker/web/nspanelmanager/firmware/custom/data_file.bin differ diff --git a/docker/web/nspanelmanager/firmware/custom/firmware.bin b/docker/web/nspanelmanager/firmware/custom/firmware.bin new file mode 100644 index 00000000..7b6e7371 Binary files /dev/null and b/docker/web/nspanelmanager/firmware/custom/firmware.bin differ diff --git a/docker/web/nspanelmanager/firmware/custom/merged_flash.bin b/docker/web/nspanelmanager/firmware/custom/merged_flash.bin new file mode 100644 index 00000000..4adbf4e1 Binary files /dev/null and b/docker/web/nspanelmanager/firmware/custom/merged_flash.bin differ diff --git a/docker/web/nspanelmanager/firmware/sonoff/data_file.bin b/docker/web/nspanelmanager/firmware/sonoff/data_file.bin new file mode 100644 index 00000000..ec5e844b Binary files /dev/null and b/docker/web/nspanelmanager/firmware/sonoff/data_file.bin differ diff --git a/docker/web/nspanelmanager/firmware/sonoff/firmware.bin b/docker/web/nspanelmanager/firmware/sonoff/firmware.bin new file mode 100644 index 00000000..ef2507f3 Binary files /dev/null and b/docker/web/nspanelmanager/firmware/sonoff/firmware.bin differ diff --git a/docker/web/nspanelmanager/merged_flash.bin b/docker/web/nspanelmanager/firmware/sonoff/merged_flash.bin similarity index 76% rename from docker/web/nspanelmanager/merged_flash.bin rename to docker/web/nspanelmanager/firmware/sonoff/merged_flash.bin index 1984b7b1..46b13bf3 100644 Binary files a/docker/web/nspanelmanager/merged_flash.bin and b/docker/web/nspanelmanager/firmware/sonoff/merged_flash.bin differ diff --git a/docker/web/nspanelmanager/gui.tft b/docker/web/nspanelmanager/gui.tft deleted file mode 100644 index e186f437..00000000 Binary files a/docker/web/nspanelmanager/gui.tft and /dev/null differ diff --git a/docker/web/nspanelmanager/gui2.tft b/docker/web/nspanelmanager/gui2.tft deleted file mode 100644 index a3d3e912..00000000 Binary files a/docker/web/nspanelmanager/gui2.tft and /dev/null differ diff --git a/docker/web/nspanelmanager/gui3.tft b/docker/web/nspanelmanager/gui3.tft deleted file mode 100644 index 3eeabd55..00000000 Binary files a/docker/web/nspanelmanager/gui3.tft and /dev/null differ diff --git a/docker/web/nspanelmanager/gui4.tft b/docker/web/nspanelmanager/gui4.tft deleted file mode 100644 index df3b2bf9..00000000 Binary files a/docker/web/nspanelmanager/gui4.tft and /dev/null differ diff --git a/docker/web/nspanelmanager/gui_us.tft b/docker/web/nspanelmanager/gui_us.tft deleted file mode 100644 index c07ebd57..00000000 Binary files a/docker/web/nspanelmanager/gui_us.tft and /dev/null differ diff --git a/docker/web/nspanelmanager/gui_us2.tft b/docker/web/nspanelmanager/gui_us2.tft deleted file mode 100644 index ed053469..00000000 Binary files a/docker/web/nspanelmanager/gui_us2.tft and /dev/null differ diff --git a/docker/web/nspanelmanager/gui_us3.tft b/docker/web/nspanelmanager/gui_us3.tft deleted file mode 100644 index 7629c06b..00000000 Binary files a/docker/web/nspanelmanager/gui_us3.tft and /dev/null differ diff --git a/docker/web/nspanelmanager/gui_us4.tft b/docker/web/nspanelmanager/gui_us4.tft deleted file mode 100644 index 86d037bd..00000000 Binary files a/docker/web/nspanelmanager/gui_us4.tft and /dev/null differ diff --git a/docker/web/nspanelmanager/web/api.py b/docker/web/nspanelmanager/web/api.py index 1d5e0b88..a78d1c93 100644 --- a/docker/web/nspanelmanager/web/api.py +++ b/docker/web/nspanelmanager/web/api.py @@ -1,24 +1,29 @@ -from django.http import HttpResponse, JsonResponse -from django.shortcuts import render, redirect -from datetime import datetime -from django.views.decorators.csrf import csrf_exempt -from django.core.files.storage import FileSystemStorage +import hashlib import json -import requests import logging -import traceback - -import hashlib -import psutil +import os import signal import subprocess +import traceback +from datetime import datetime + import environ -import os -import logging +import psutil +import requests +from django.core.files.storage import FileSystemStorage +from django.http import HttpResponse, JsonResponse +from django.shortcuts import redirect, render +from django.views.decorators.csrf import csrf_exempt + +from web.settings_helper import ( + get_nspanel_setting_with_default, + get_setting_with_default, + set_setting_value, +) from .apps import restart_mqtt_manager_process -from .models import NSPanel, Room, LightState, Scene, RelayGroup -from web.settings_helper import get_setting_with_default, get_nspanel_setting_with_default, set_setting_value +from .models import LightState, NSPanel, RelayGroup, Room, Scene + def get_nspanel_json_representation(panel): panel_config = { @@ -30,10 +35,11 @@ def get_nspanel_json_representation(panel): "address": panel.ip_address, "relay1_is_light": get_nspanel_setting_with_default(panel.id, "relay1_is_light", "False") == "True", "relay2_is_light": get_nspanel_setting_with_default(panel.id, "relay2_is_light", "False") == "True", - "denied": "True" if panel.denied else "False" + "denied": "True" if panel.denied else "False", } return panel_config + # TODO: Rework how available entities are gathered def get_all_available_entities(request): # TODO: Implement manually entered entities @@ -48,10 +54,7 @@ def get_all_available_entities(request): openhab_type_filter = filter_data["openhab_type_filter"] # Get Home Assistant lights - return_json = { - "entities": [], - "errors": [] - } + return_json = {"entities": [], "errors": []} # Home Assistant if get_setting_with_default("home_assistant_token") != "" and get_setting_with_default("home_assistant_address") != "": @@ -66,7 +69,12 @@ def get_all_available_entities(request): else: home_assistant_api_address = get_setting_with_default("home_assistant_address") + "/api/states" logging.debug("Trying to get Home Assistant entities via api address: " + home_assistant_api_address) - home_assistant_response = requests.get(home_assistant_api_address, headers=home_assistant_request_headers, timeout=5, verify=False) + home_assistant_response = requests.get( + home_assistant_api_address, + headers=home_assistant_request_headers, + timeout=5, + verify=False, + ) if home_assistant_response.status_code == 200: for entity in home_assistant_response.json(): entity_type = entity["entity_id"].split(".")[0] @@ -89,13 +97,10 @@ def get_all_available_entities(request): return_json["entities"].append(data) else: - return_json["errors"].append( - "Failed to get Home Assistant lights, got return code: " + str(home_assistant_response.status_code)) - print("ERROR! Got status code other than 200. Got code: " + - str(home_assistant_response.status_code)) + return_json["errors"].append("Failed to get Home Assistant lights, got return code: " + str(home_assistant_response.status_code)) + print("ERROR! Got status code other than 200. Got code: " + str(home_assistant_response.status_code)) except Exception as e: - return_json["errors"].append( - "Failed to get Home Assistant lights: " + str(traceback.format_exc())) + return_json["errors"].append("Failed to get Home Assistant lights: " + str(traceback.format_exc())) logging.exception("Failed to get Home Assistant lights!") else: print("No home assistant configuration values. Will not gather Home Assistant entities.") @@ -109,8 +114,11 @@ def get_all_available_entities(request): } try: if "things" in openhab_type_filter: - openhab_response = requests.get(get_setting_with_default( - "openhab_address") + "/rest/things", headers=openhab_request_headers, verify=False) + openhab_response = requests.get( + get_setting_with_default("openhab_address") + "/rest/things", + headers=openhab_request_headers, + verify=False, + ) if openhab_response.status_code == 200: for entity in openhab_response.json(): @@ -120,7 +128,12 @@ def get_all_available_entities(request): for channel in entity["channels"]: # Check if this thing has a channel that indicates that it might be a light add_items_with_channels_of_type = [ - "Dimmer", "Number", "Color", "Switch", "String"] + "Dimmer", + "Number", + "Color", + "Switch", + "String", + ] if "itemType" in channel and (channel["itemType"] in add_items_with_channels_of_type): add_entity = True if "linkedItems" in channel: @@ -130,42 +143,44 @@ def get_all_available_entities(request): items.append(linkedItem) if add_entity: # return_json["openhab_lights"].append(entity["label"]) - return_json["entities"].append({ - "type": "openhab", - "openhab_type": "thing", - "label": entity["label"], - "entity_id": entity["label"], - "items": items, - "raw_data": entity, - }) + return_json["entities"].append( + { + "type": "openhab", + "openhab_type": "thing", + "label": entity["label"], + "entity_id": entity["label"], + "items": items, + "raw_data": entity, + } + ) else: - return_json["errors"].append( - "Failed to get OpenHAB lights, got return code: " + str(openhab_response.status_code)) - print("ERROR! Got status code other than 200. Got code: " + - str(openhab_response.status_code)) + return_json["errors"].append("Failed to get OpenHAB lights, got return code: " + str(openhab_response.status_code)) + print("ERROR! Got status code other than 200. Got code: " + str(openhab_response.status_code)) elif "rules" in openhab_type_filter: - openhab_response = requests.get(get_setting_with_default( - "openhab_address") + "/rest/rules", headers=openhab_request_headers, verify=False) + openhab_response = requests.get( + get_setting_with_default("openhab_address") + "/rest/rules", + headers=openhab_request_headers, + verify=False, + ) if openhab_response.status_code == 200: for entity in openhab_response.json(): if "name" in entity: - return_json["entities"].append({ - "type": "openhab", - "openhab_type": "rule", - "label": entity["name"], - "entity_id": entity["uid"], - "raw_data": entity, - "items": [] - }) + return_json["entities"].append( + { + "type": "openhab", + "openhab_type": "rule", + "label": entity["name"], + "entity_id": entity["uid"], + "raw_data": entity, + "items": [], + } + ) else: - return_json["errors"].append( - "Failed to get OpenHAB lights, got return code: " + str(openhab_response.status_code)) - print("ERROR! Got status code other than 200. Got code: " + - str(openhab_response.status_code)) + return_json["errors"].append("Failed to get OpenHAB lights, got return code: " + str(openhab_response.status_code)) + print("ERROR! Got status code other than 200. Got code: " + str(openhab_response.status_code)) except Exception as e: - return_json["errors"].append( - "Failed to get OpenHAB lights: " + str(traceback.format_exc())) + return_json["errors"].append("Failed to get OpenHAB lights: " + str(traceback.format_exc())) logging.exception("Failed to get OpenHAB lights!") else: print("No OpenHAB configuration values. Will not gather OpenHAB entities.") @@ -173,66 +188,48 @@ def get_all_available_entities(request): return return_json - def get_nspanel_config(request): try: - logging.info("Trying to load config for NSPanel with MAC " + request.GET['mac']) + logging.info("Trying to load config for NSPanel with MAC " + request.GET["mac"]) nspanel = NSPanel.objects.get(mac_address=request.GET["mac"]) base = {} base["name"] = nspanel.friendly_name base["home"] = nspanel.room.id - base["default_page"] = get_nspanel_setting_with_default( - nspanel.id, "default_page", "0") - base["raise_to_100_light_level"] = get_setting_with_default( - "raise_to_100_light_level") - base["color_temp_min"] = get_setting_with_default( - "color_temp_min") - base["color_temp_max"] = get_setting_with_default( - "color_temp_max") - base["reverse_color_temp"] = get_setting_with_default( - "reverse_color_temp") - base["min_button_push_time"] = get_setting_with_default( - "min_button_push_time") - base["button_long_press_time"] = get_setting_with_default( - "button_long_press_time") - base["special_mode_trigger_time"] = get_setting_with_default( - "special_mode_trigger_time") - base["special_mode_release_time"] = get_setting_with_default( - "special_mode_release_time") - base["screen_dim_level"] = get_nspanel_setting_with_default( - nspanel.id, "screen_dim_level", get_setting_with_default("screen_dim_level")) + base["default_page"] = get_nspanel_setting_with_default(nspanel.id, "default_page", "0") + base["raise_to_100_light_level"] = get_setting_with_default("raise_to_100_light_level") + base["color_temp_min"] = get_setting_with_default("color_temp_min") + base["color_temp_max"] = get_setting_with_default("color_temp_max") + base["reverse_color_temp"] = get_setting_with_default("reverse_color_temp") + base["min_button_push_time"] = get_setting_with_default("min_button_push_time") + base["button_long_press_time"] = get_setting_with_default("button_long_press_time") + base["special_mode_trigger_time"] = get_setting_with_default("special_mode_trigger_time") + base["special_mode_release_time"] = get_setting_with_default("special_mode_release_time") + base["screen_dim_level"] = get_nspanel_setting_with_default(nspanel.id, "screen_dim_level", get_setting_with_default("screen_dim_level")) base["screensaver_dim_level"] = get_nspanel_setting_with_default( - nspanel.id, "screensaver_dim_level", get_setting_with_default("screensaver_dim_level")) + nspanel.id, + "screensaver_dim_level", + get_setting_with_default("screensaver_dim_level"), + ) base["screensaver_activation_timeout"] = get_nspanel_setting_with_default( - nspanel.id, "screensaver_activation_timeout", get_setting_with_default("screensaver_activation_timeout")) - base["screensaver_mode"] = get_nspanel_setting_with_default( - nspanel.id, "screensaver_mode", get_setting_with_default("screensaver_mode")) - base["clock_us_style"] = get_setting_with_default( - "clock_us_style") - base["use_fahrenheit"] = get_setting_with_default( - "use_fahrenheit") - base["is_us_panel"] = get_nspanel_setting_with_default( - nspanel.id, "is_us_panel", "False") - base["lock_to_default_room"] = get_nspanel_setting_with_default( - nspanel.id, "lock_to_default_room", "False") - base["reverse_relays"] = get_nspanel_setting_with_default( - nspanel.id, "reverse_relays", False) - base["relay1_default_mode"] = get_nspanel_setting_with_default( - nspanel.id, "relay1_default_mode", "False") - base["relay2_default_mode"] = get_nspanel_setting_with_default( - nspanel.id, "relay2_default_mode", "False") - base["temperature_calibration"] = float( - get_nspanel_setting_with_default(nspanel.id, "temperature_calibration", 0)) + nspanel.id, + "screensaver_activation_timeout", + get_setting_with_default("screensaver_activation_timeout"), + ) + base["screensaver_mode"] = get_nspanel_setting_with_default(nspanel.id, "screensaver_mode", get_setting_with_default("screensaver_mode")) + base["clock_us_style"] = get_setting_with_default("clock_us_style") + base["use_fahrenheit"] = get_setting_with_default("use_fahrenheit") + base["is_us_panel"] = get_nspanel_setting_with_default(nspanel.id, "is_us_panel", "False") + base["lock_to_default_room"] = get_nspanel_setting_with_default(nspanel.id, "lock_to_default_room", "False") + base["reverse_relays"] = get_nspanel_setting_with_default(nspanel.id, "reverse_relays", False) + base["relay1_default_mode"] = get_nspanel_setting_with_default(nspanel.id, "relay1_default_mode", "False") + base["relay2_default_mode"] = get_nspanel_setting_with_default(nspanel.id, "relay2_default_mode", "False") + base["temperature_calibration"] = float(get_nspanel_setting_with_default(nspanel.id, "temperature_calibration", 0)) base["button1_mode"] = nspanel.button1_mode - base["button1_mqtt_topic"] = get_nspanel_setting_with_default( - nspanel.id, "button1_mqtt_topic", "") - base["button1_mqtt_payload"] = get_nspanel_setting_with_default( - nspanel.id, "button1_mqtt_payload", "") + base["button1_mqtt_topic"] = get_nspanel_setting_with_default(nspanel.id, "button1_mqtt_topic", "") + base["button1_mqtt_payload"] = get_nspanel_setting_with_default(nspanel.id, "button1_mqtt_payload", "") base["button2_mode"] = nspanel.button2_mode - base["button2_mqtt_topic"] = get_nspanel_setting_with_default( - nspanel.id, "button2_mqtt_topic", "") - base["button2_mqtt_payload"] = get_nspanel_setting_with_default( - nspanel.id, "button2_mqtt_payload", "") + base["button2_mqtt_topic"] = get_nspanel_setting_with_default(nspanel.id, "button2_mqtt_topic", "") + base["button2_mqtt_payload"] = get_nspanel_setting_with_default(nspanel.id, "button2_mqtt_payload", "") if nspanel.button1_detached_mode_light: base["button1_detached_light"] = nspanel.button1_detached_mode_light.id @@ -244,7 +241,7 @@ def get_nspanel_config(request): else: base["button2_detached_light"] = -1 base["rooms"] = [] - for room in Room.objects.all().order_by('displayOrder'): + for room in Room.objects.all().order_by("displayOrder"): base["rooms"].append(room.id) base["scenes"] = {} for scene in Scene.objects.filter(room__isnull=True): @@ -276,7 +273,7 @@ def set_panel_status(request, panel_mac: str): if nspanels.exists(): nspanel = nspanels.first() # We got a match - json_payload = json.loads(request.body.decode('utf-8')) + json_payload = json.loads(request.body.decode("utf-8")) nspanel.wifi_rssi = int(json_payload["rssi"]) nspanel.heap_used_pct = int(json_payload["heap_used_pct"]) nspanel.temperature = round(json_payload["temperature"], 2) @@ -293,8 +290,8 @@ def set_panel_online_status(request, panel_mac: str): if nspanels.exists(): nspanel = nspanels.first() # We got a match - payload = json.loads(request.body.decode('utf-8')) - nspanel.online_state = (payload["state"] == "online") + payload = json.loads(request.body.decode("utf-8")) + nspanel.online_state = payload["state"] == "online" nspanel.save() return HttpResponse("", status=200) @@ -310,18 +307,20 @@ def get_scenes(request): "scene_name": scene.friendly_name, "room_name": scene.room.friendly_name if scene.room != None else None, "room_id": scene.room.id if scene.room != None else None, - "light_states": [] + "light_states": [], } for state in scene.lightstate_set.all(): - scene_info["light_states"].append({ - "light_id": state.light.id, - "light_type": state.light.type, - "color_mode": state.color_mode, - "light_level": state.light_level, - "color_temp": state.color_temperature, - "hue": state.hue, - "saturation": state.saturation - }) + scene_info["light_states"].append( + { + "light_id": state.light.id, + "light_type": state.light.type, + "color_mode": state.color_mode, + "light_level": state.light_level, + "color_temp": state.color_temperature, + "hue": state.hue, + "saturation": state.saturation, + } + ) return_json["scenes"].append(scene_info) return JsonResponse(return_json) @@ -336,3 +335,20 @@ def restart_mqtt_manager(request): def save_theme(request): set_setting_value("dark_theme", request.POST["dark"]) return HttpResponse("OK", status=200) + + +# @csrf_exempt +# def get_album_cover(request): +# from wand.image import Image +# combined_pixel_data = [] +# with Image(filename="/usr/src/app/nspanelmanager/output_resized.tiff") as img: +# pixel_data = img.export_pixels(channel_map="RGB", storage="char") +# for i in range(0, len(pixel_data), 3): +# channel_r = pixel_data[i] >> 3 +# channel_g = pixel_data[i + 1] >> 2 +# channel_b = pixel_data[i + 2] >> 3 +# combined = ( +# channel_r << 11 | channel_g << 5 | channel_b +# ) # Combine all values into a single 16-bit color value (ie. 565 colors) +# combined_pixel_data.append(combined) +# return JsonResponse({"pixels": combined_pixel_data}, status=200) diff --git a/docker/web/nspanelmanager/web/apps.py b/docker/web/nspanelmanager/web/apps.py index add16759..3b8bbc0e 100755 --- a/docker/web/nspanelmanager/web/apps.py +++ b/docker/web/nspanelmanager/web/apps.py @@ -1,29 +1,32 @@ -from django.apps import AppConfig -import environ import logging -import psutil -import subprocess import os import signal -import time +import subprocess import sys -import signal +import time + +import environ +import psutil +from django.apps import AppConfig + from web.protobuf import protobuf_mqttmanager_pb2 mqttmanager_process = None + def sigchld_handler(signum, frame): # Handle SIGCHLD from mqttmanager_process pid, status = os.waitpid(-1, os.WNOHANG) if pid > 0: if os.WIFEXITED(status): - logging.error(F"MQTTManager binary has exited unexpectedly. Return code: {os.WEXITSTATUS(status)}") + logging.error(f"MQTTManager binary has exited unexpectedly. Return code: {os.WEXITSTATUS(status)}") elif os.WIFSIGNALED(status): - logging.error(F"MQTTManager binary has exited unexpectedly. Killed by signal: {os.WTERMSIG(status)}") + logging.error(f"MQTTManager binary has exited unexpectedly. Killed by signal: {os.WTERMSIG(status)}") def start_mqtt_manager(): from .settings_helper import get_setting_with_default + global mqttmanager_process print("Did not find a running MQTTManager, starting MQTTManager...") @@ -38,6 +41,8 @@ def start_mqtt_manager(): """ Restart the MQTT Manager process """ + + def restart_mqtt_manager_process(): for proc in psutil.process_iter(): try: @@ -56,6 +61,8 @@ def restart_mqtt_manager_process(): """ Send command to MQTT Manager to reload config """ + + def send_mqttmanager_reload_command(): for proc in psutil.process_iter(): if proc.status() == psutil.STATUS_ZOMBIE: @@ -68,6 +75,7 @@ def send_mqttmanager_reload_command(): def create_entity_pages_for_all_rooms(): from .models import Room, RoomEntitiesPage + for room in Room.objects.all(): # Check if an existing scenes page exists, if it does not, create one existing_pages = RoomEntitiesPage.objects.filter(room=room, is_scenes_page=True) @@ -92,6 +100,7 @@ def create_entity_pages_for_all_rooms(): def create_global_scenes_page(): from .models import RoomEntitiesPage + existing_pages = RoomEntitiesPage.objects.filter(room=None, is_scenes_page=True) if not existing_pages.exists(): page = RoomEntitiesPage() @@ -103,8 +112,8 @@ def create_global_scenes_page(): class WebConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'web' + default_auto_field = "django.db.models.BigAutoField" + name = "web" def ready(self): # Only start MQTT Manager and load values if we are actually starting the application @@ -120,18 +129,14 @@ def ready(self): if "IS_HOME_ASSISTANT_ADDON" in environment and environment("IS_HOME_ASSISTANT_ADDON") == "true": if "SUPERVISOR_TOKEN" in environment: from .settings_helper import get_setting_with_default, set_setting_value + if get_setting_with_default("home_assistant_token") == "" and get_setting_with_default("home_assistant_address") == "": - print( - "No home assistant address or token stored, setting according to addon environment.") - set_setting_value( - "home_assistant_token", environment("SUPERVISOR_TOKEN")) - set_setting_value( - "home_assistant_address", "http://supervisor") + print("No home assistant address or token stored, setting according to addon environment.") + set_setting_value("home_assistant_token", environment("SUPERVISOR_TOKEN")) + set_setting_value("home_assistant_address", "http://supervisor") elif get_setting_with_default("home_assistant_token") != environment("SUPERVISOR_TOKEN"): - print( - "Home Assistant token has changed. Will update database.") - set_setting_value( - "home_assistant_token", environment("SUPERVISOR_TOKEN")) + print("Home Assistant token has changed. Will update database.") + set_setting_value("home_assistant_token", environment("SUPERVISOR_TOKEN")) # from .models import Settings # objects = Settings.objects.filter(name=name) @@ -139,5 +144,4 @@ def ready(self): create_global_scenes_page() restart_mqtt_manager_process() except: - logging.exception( - "Failed to populate Home Assistant addon settings.") + logging.exception("Failed to populate Home Assistant addon settings.") diff --git a/docker/web/nspanelmanager/web/components/alert/alert.html b/docker/web/nspanelmanager/web/components/alert/alert.html index 893171d5..fd9da091 100644 --- a/docker/web/nspanelmanager/web/components/alert/alert.html +++ b/docker/web/nspanelmanager/web/components/alert/alert.html @@ -1,20 +1,20 @@ {% if level == "info" %} -