mirror of https://github.com/lumapu/ahoy.git
				
				
			
				 73 changed files with 6267 additions and 3228 deletions
			
			
		| 
		 After Width: | Height: | Size: 650 KiB  | 
| 
		 After Width: | Height: | Size: 66 KiB  | 
@ -0,0 +1,51 @@ | 
				
			|||||
 | 
					# Prometheus Endpoint | 
				
			||||
 | 
					Metrics available for AhoyDTU device, inverters and channels. | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					Prometheus metrics provided at `/metrics`.  | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					## Labels | 
				
			||||
 | 
					| Label name   | Description                           |  | 
				
			||||
 | 
					|:-------------|:--------------------------------------| | 
				
			||||
 | 
					| version      | current installed version of AhoyDTU  | | 
				
			||||
 | 
					| image        | currently not used                    | | 
				
			||||
 | 
					| devicename   | Device name from setup                | | 
				
			||||
 | 
					| name         | Inverter name from setup              | | 
				
			||||
 | 
					| serial       | Serial number of inverter             | | 
				
			||||
 | 
					| inverter     | Inverter name from setup              | | 
				
			||||
 | 
					| channel      | Channel name from setup               | | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					## Exported Metrics | 
				
			||||
 | 
					| Metric name                            | Type    | Description                                            | Labels       |  | 
				
			||||
 | 
					|----------------------------------------|---------|--------------------------------------------------------|--------------| | 
				
			||||
 | 
					| `ahoy_solar_info`                      | Gauge   | Information about the AhoyDTU device                   | version, image, devicename | | 
				
			||||
 | 
					| `ahoy_solar_uptime`                    | Counter | Seconds since boot of the AhoyDTU device               | devicename | | 
				
			||||
 | 
					| `ahoy_solar_rssi_db`                   | Gauge   | Quality of the Wifi STA connection                     | devicename | | 
				
			||||
 | 
					| `ahoy_solar_inverter_info`             | Gauge   | Information about the configured inverter(s)           | name, serial | | 
				
			||||
 | 
					| `ahoy_solar_inverter_enabled`          | Gauge   | Is the inverter enabled?                               | inverter  | | 
				
			||||
 | 
					| `ahoy_solar_inverter_is_available`     | Gauge   | is the inverter available?                             | inverter  | | 
				
			||||
 | 
					| `ahoy_solar_inverter_is_producing`     | Gauge   | Is the inverter producing?                             | inverter  | | 
				
			||||
 | 
					| `ahoy_solar_U_AC_volt`                 | Gauge   | AC voltage of inverter [V]                             | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_I_AC_ampere`               | Gauge   | AC current of inverter [A]                             | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_P_AC_watt`                 | Gauge   | AC power of inverter [W]                               | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_Q_AC_var`                  | Gauge   | AC reactive power[var]                                 | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_F_AC_hertz`                | Gauge   | AC frequency [Hz]                                      | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_PF_AC`                     | Gauge   | AC Power factor                                        | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_Temp_celsius`              | Gauge   | Temperature of inverter                                | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_ALARM_MES_ID`              | Gauge   | Alarm message index of inverter                        | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_LastAlarmCode`             | Gauge   | Last alarm code from inverter                          | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_YieldDay_wattHours`        | Counter | Energy converted to AC per day [Wh]                    | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_YieldTotal_kilowattHours`  | Counter | Energy converted to AC since reset [kWh]               | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_P_DC_watt`                 | Gauge   | DC power of inverter [W]                               | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_Efficiency_ratio`          | Gauge   | ration AC Power over DC Power [%]                      | inverter  |  | 
				
			||||
 | 
					| `ahoy_solar_U_DC_volt`                 | Gauge   | DC voltage of channel [V]                              | inverter, channel |  | 
				
			||||
 | 
					| `ahoy_solar_I_DC_ampere`               | Gauge   | DC current of channel [A]                              | inverter, channel |  | 
				
			||||
 | 
					| `ahoy_solar_P_DC_watt`                 | Gauge   | DC power of channel [P]                                | inverter, channel |  | 
				
			||||
 | 
					| `ahoy_solar_YieldDay_wattHours`        | Counter | Energy converted to AC per day [Wh]                    | inverter, channel |  | 
				
			||||
 | 
					| `ahoy_solar_YieldTotal_kilowattHours`  | Counter | Energy converted to AC since reset [kWh]               | inverter, channel |  | 
				
			||||
 | 
					| `ahoy_solar_Irradiation_ratio`         | Gauge   | ratio DC Power over set maximum power per channel [%]  | inverter, channel | | 
				
			||||
 | 
					| `ahoy_solar_radio_rx_success`          | Gauge   | NRF24 statistic                                        | | | 
				
			||||
 | 
					| `ahoy_solar_radio_rx_fail`             | Gauge   | NRF24 statistic                                        | | | 
				
			||||
 | 
					| `ahoy_solar_radio_rx_fail_answer`      | Gauge   | NRF24 statistic                                        | | | 
				
			||||
 | 
					| `ahoy_solar_radio_frame_cnt`           | Gauge   | NRF24 statistic                                        | | | 
				
			||||
 | 
					| `ahoy_solar_radio_tx_cnt`              | Gauge   | NRF24 statistic                                        | | | 
				
			||||
 | 
					
 | 
				
			||||
@ -1,36 +1,33 @@ | 
				
			|||||
# Changelog v0.5.66 | 
					Changelog v0.6.0 | 
				
			||||
 | 
					
 | 
				
			||||
**Note:** Version `0.5.42` to `0.5.65` were development versions. Last release version was `0.5.41` | 
					## General | 
				
			||||
Detailed change log (development changes): https://github.com/lumapu/ahoy/blob/945a671d27d10d0f7c175ebbf2fbb2806f9cd79a/src/CHANGES.md | 
					* improved night time calculation time to 1 minute after last communication pause #515 | 
				
			||||
 | 
					* refactored code for better readability | 
				
			||||
 | 
					* improved Hoymiles commuinication (retransmits, immediate power limit transmission, timing at all) | 
				
			||||
 | 
					* renamed firmware binaries | 
				
			||||
 | 
					* add login / logout to menu | 
				
			||||
 | 
					* add display support for `SH1106`, `SSD1306`, `Nokia` and `ePaper 1.54"` (ESP32 only) | 
				
			||||
 | 
					* add yield total correction - move your yield to a new inverter or correct an already used inverter | 
				
			||||
 | 
					* added import / export feature | 
				
			||||
 | 
					* added `Prometheus` endpoints | 
				
			||||
 | 
					* improved wifi connection and stability (connect to strongest AP) | 
				
			||||
 | 
					* addded Hoymiles alarm IDs to log | 
				
			||||
 | 
					* improved `System` information page (eg. radio statitistics) | 
				
			||||
 | 
					* improved UI (repsonsive design, (optional) dark mode) | 
				
			||||
 | 
					* improved system stability (reduced `heap-fragmentation`, don't break settings on failure) #644, #645 | 
				
			||||
 | 
					* added support for 2nd generation of Hoymiles inverters, MI series | 
				
			||||
 | 
					* improved JSON API for more stable WebUI | 
				
			||||
 | 
					* added option to disable input display in `/live` (`max-power` has to be set to `0`) | 
				
			||||
 | 
					* updated documentation | 
				
			||||
 | 
					* improved settings on ESP32 devices while setting SPI pins (for `NRF24` radio) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					## MqTT | 
				
			||||
* updated REST API and MQTT (both of them use the same functionality) | 
					* added `comm_disabled` #529 | 
				
			||||
* improved stability | 
					* added fixed interval option #542, #523 | 
				
			||||
* Regular expressions for input fields which are used for MQTT to be compliant to MQTT | 
					* improved communication, only required publishes | 
				
			||||
* WiFi optimization (AP Mode and STA in parallel, reconnect if local STA is unavailable) | 
					* improved retained flags | 
				
			||||
* improved display of `/system` | 
					* added `set_power_limit` acknowledge MQTT publish #553 | 
				
			||||
* fix Update button protection (prevent double click #527) | 
					* added feature to reset values on midnight, communication pause or if the inverters are not available | 
				
			||||
* optimized scheduler #515 | 
					* partially added Hoymiles alarm ID | 
				
			||||
* fix of duplicates in API `/api/record/live` (#526) | 
					* improved autodiscover (added total values on multi-inverter setup) | 
				
			||||
* added update information to `index.html` (check for update with github.com) | 
					* improved `clientID` a part of the MAC address is added to have an unique name | 
				
			||||
* fix web logout (auto logout) | 
					 | 
				
			||||
* switched MQTT library | 
					 | 
				
			||||
* removed MQTT `available_text` (can be deducted from `available`) | 
					 | 
				
			||||
* enhanced MQTT documentation in `User_Manual.md` | 
					 | 
				
			||||
* changed MQTT topic `status` to nummeric value, check documentation in `User_Manual.md` | 
					 | 
				
			||||
* added immediate (each minute) report of inverter status MQTT #522 | 
					 | 
				
			||||
* increased MQTT user, pwd and topic length to 64 characters + `\0`. (The string end `\0` reduces the available size by one) #516 | 
					 | 
				
			||||
* added disable night communication flag to MQTT #505 | 
					 | 
				
			||||
* added MQTT <TOPIC>/status to show status over all inverters | 
					 | 
				
			||||
* added MQTT RX counter to index.html | 
					 | 
				
			||||
* added protection mask to select which pages should be protected | 
					 | 
				
			||||
* added monochrome display that show values also if nothing changed and in offline mode #498 | 
					 | 
				
			||||
* added icons to index.html, added WiFi-strength symbol on each page | 
					 | 
				
			||||
* refactored communication offset (adjustable in minutes now) | 
					 | 
				
			||||
* factory reset formats entire little fs | 
					 | 
				
			||||
* renamed sunrise / sunset on index.html to start / stop communication | 
					 | 
				
			||||
* fixed static IP save | 
					 | 
				
			||||
* fix NTP with static IP | 
					 | 
				
			||||
* all values are displayed on /live even if they are 0 | 
					 | 
				
			||||
* added NRF24 info to Systeminfo | 
					 | 
				
			||||
* reordered enqueue commands after boot up to prevent same payload length for successive commands | 
					 | 
				
			||||
 | 
				
			|||||
@ -1,161 +0,0 @@ | 
				
			|||||
/*
 | 
					 | 
				
			||||
    CircularBuffer - An Arduino circular buffering library for arbitrary types. | 
					 | 
				
			||||
 | 
					 | 
				
			||||
    Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl | 
					 | 
				
			||||
 | 
					 | 
				
			||||
    This library is free software; you can redistribute it and/or | 
					 | 
				
			||||
    modify it under the terms of the GNU Lesser General Public | 
					 | 
				
			||||
    License as published by the Free Software Foundation; either | 
					 | 
				
			||||
    version 2.1 of the License, or (at your option) any later version. | 
					 | 
				
			||||
 | 
					 | 
				
			||||
    This library is distributed in the hope that it will be useful, | 
					 | 
				
			||||
    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					 | 
				
			||||
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
					 | 
				
			||||
    Lesser General Public License for more details. | 
					 | 
				
			||||
 | 
					 | 
				
			||||
    You should have received a copy of the GNU Lesser General Public | 
					 | 
				
			||||
    License along with this library; if not, write to the Free Software | 
					 | 
				
			||||
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | 
					 | 
				
			||||
*/ | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#ifndef CircularBuffer_h | 
					 | 
				
			||||
#define CircularBuffer_h | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#if defined(ESP8266) || defined(ESP32) | 
					 | 
				
			||||
#define DISABLE_IRQ noInterrupts() | 
					 | 
				
			||||
#define RESTORE_IRQ interrupts() | 
					 | 
				
			||||
#else | 
					 | 
				
			||||
#define DISABLE_IRQ       \ | 
					 | 
				
			||||
    uint8_t sreg = SREG;    \ | 
					 | 
				
			||||
    cli(); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#define RESTORE_IRQ        \ | 
					 | 
				
			||||
    SREG = sreg; | 
					 | 
				
			||||
#endif | 
					 | 
				
			||||
 | 
					 | 
				
			||||
template <class BUFFERTYPE, uint8_t BUFFERSIZE> | 
					 | 
				
			||||
class CircularBuffer { | 
					 | 
				
			||||
 | 
					 | 
				
			||||
    typedef BUFFERTYPE BufferType; | 
					 | 
				
			||||
    BufferType Buffer[BUFFERSIZE]; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
    public: | 
					 | 
				
			||||
        CircularBuffer() : m_buff(Buffer) { | 
					 | 
				
			||||
            m_size = BUFFERSIZE; | 
					 | 
				
			||||
            clear(); | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        /** Clear all entries in the circular buffer. */ | 
					 | 
				
			||||
        void clear(void) | 
					 | 
				
			||||
        { | 
					 | 
				
			||||
            m_front = 0; | 
					 | 
				
			||||
            m_fill  = 0; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        /** Test if the circular buffer is empty */ | 
					 | 
				
			||||
        inline bool empty(void) const | 
					 | 
				
			||||
        { | 
					 | 
				
			||||
            return !m_fill; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        /** Return the number of records stored in the buffer */ | 
					 | 
				
			||||
        inline uint8_t available(void) const | 
					 | 
				
			||||
        { | 
					 | 
				
			||||
            return m_fill; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        /** Test if the circular buffer is full */ | 
					 | 
				
			||||
        inline bool full(void) const | 
					 | 
				
			||||
        { | 
					 | 
				
			||||
            return m_fill == m_size; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        inline uint8_t getFill(void) const { | 
					 | 
				
			||||
            return m_fill; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        /** Aquire record on front of the buffer, for writing.
 | 
					 | 
				
			||||
         * After filling the record, it has to be pushed to actually | 
					 | 
				
			||||
         * add it to the buffer. | 
					 | 
				
			||||
         * @return Pointer to record, or NULL when buffer is full. | 
					 | 
				
			||||
         */ | 
					 | 
				
			||||
        BUFFERTYPE* getFront(void) const | 
					 | 
				
			||||
        { | 
					 | 
				
			||||
            DISABLE_IRQ; | 
					 | 
				
			||||
            BUFFERTYPE* f = NULL; | 
					 | 
				
			||||
            if (!full()) | 
					 | 
				
			||||
                f = get(m_front); | 
					 | 
				
			||||
            RESTORE_IRQ; | 
					 | 
				
			||||
            return f; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        /** Push record to front of the buffer
 | 
					 | 
				
			||||
         * @param record   Record to push. If record was aquired previously (using getFront) its | 
					 | 
				
			||||
         *                 data will not be copied as it is already present in the buffer. | 
					 | 
				
			||||
         * @return True, when record was pushed successfully. | 
					 | 
				
			||||
         */ | 
					 | 
				
			||||
        bool pushFront(BUFFERTYPE* record) | 
					 | 
				
			||||
        { | 
					 | 
				
			||||
            bool ok = false; | 
					 | 
				
			||||
            DISABLE_IRQ; | 
					 | 
				
			||||
            if (!full()) | 
					 | 
				
			||||
            { | 
					 | 
				
			||||
                BUFFERTYPE* f = get(m_front); | 
					 | 
				
			||||
                if (f != record) | 
					 | 
				
			||||
                    *f = *record; | 
					 | 
				
			||||
                m_front = (m_front+1) % m_size; | 
					 | 
				
			||||
                m_fill++; | 
					 | 
				
			||||
                ok = true; | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            RESTORE_IRQ; | 
					 | 
				
			||||
            return ok; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        /** Aquire record on back of the buffer, for reading.
 | 
					 | 
				
			||||
         * After reading the record, it has to be pop'ed to actually | 
					 | 
				
			||||
         * remove it from the buffer. | 
					 | 
				
			||||
         * @return Pointer to record, or NULL when buffer is empty. | 
					 | 
				
			||||
         */ | 
					 | 
				
			||||
        BUFFERTYPE* getBack(void) const | 
					 | 
				
			||||
        { | 
					 | 
				
			||||
            BUFFERTYPE* b = NULL; | 
					 | 
				
			||||
            DISABLE_IRQ; | 
					 | 
				
			||||
            if (!empty()) | 
					 | 
				
			||||
                b = get(back()); | 
					 | 
				
			||||
            RESTORE_IRQ; | 
					 | 
				
			||||
            return b; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        /** Remove record from back of the buffer.
 | 
					 | 
				
			||||
         * @return True, when record was pop'ed successfully. | 
					 | 
				
			||||
         */ | 
					 | 
				
			||||
        bool popBack(void) | 
					 | 
				
			||||
        { | 
					 | 
				
			||||
            bool ok = false; | 
					 | 
				
			||||
            DISABLE_IRQ; | 
					 | 
				
			||||
            if (!empty()) | 
					 | 
				
			||||
            { | 
					 | 
				
			||||
                m_fill--; | 
					 | 
				
			||||
                ok = true; | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            RESTORE_IRQ; | 
					 | 
				
			||||
            return ok; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
    protected: | 
					 | 
				
			||||
        inline BUFFERTYPE * get(const uint8_t idx) const | 
					 | 
				
			||||
        { | 
					 | 
				
			||||
            return &(m_buff[idx]); | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
        inline uint8_t back(void) const | 
					 | 
				
			||||
        { | 
					 | 
				
			||||
            return (m_front - m_fill + m_size) % m_size; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        uint8_t          m_size;     // Total number of records that can be stored in the buffer.
 | 
					 | 
				
			||||
        BUFFERTYPE* const m_buff; | 
					 | 
				
			||||
        volatile uint8_t m_front;    // Index of front element (not pushed yet).
 | 
					 | 
				
			||||
        volatile uint8_t m_fill;     // Amount of records currently pushed.
 | 
					 | 
				
			||||
}; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#endif // CircularBuffer_h
 | 
					 | 
				
			||||
@ -0,0 +1,419 @@ | 
				
			|||||
 | 
					//-----------------------------------------------------------------------------
 | 
				
			||||
 | 
					// 2023 Ahoy, https://ahoydtu.de
 | 
				
			||||
 | 
					// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
 | 
				
			||||
 | 
					//-----------------------------------------------------------------------------
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#ifndef __HM_PAYLOAD_H__ | 
				
			||||
 | 
					#define __HM_PAYLOAD_H__ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include "../utils/dbg.h" | 
				
			||||
 | 
					#include "../utils/crc.h" | 
				
			||||
 | 
					#include "../config/config.h" | 
				
			||||
 | 
					#include <Arduino.h> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					typedef struct { | 
				
			||||
 | 
					    uint8_t txCmd; | 
				
			||||
 | 
					    uint8_t txId; | 
				
			||||
 | 
					    uint8_t invId; | 
				
			||||
 | 
					    uint32_t ts; | 
				
			||||
 | 
					    uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE]; | 
				
			||||
 | 
					    uint8_t len[MAX_PAYLOAD_ENTRIES]; | 
				
			||||
 | 
					    bool complete; | 
				
			||||
 | 
					    uint8_t maxPackId; | 
				
			||||
 | 
					    bool lastFound; | 
				
			||||
 | 
					    uint8_t retransmits; | 
				
			||||
 | 
					    bool requested; | 
				
			||||
 | 
					    bool gotFragment; | 
				
			||||
 | 
					} invPayload_t; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					typedef std::function<void(uint8_t)> payloadListenerType; | 
				
			||||
 | 
					typedef std::function<void(uint16_t alarmCode, uint32_t start, uint32_t end)> alarmListenerType; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					template<class HMSYSTEM> | 
				
			||||
 | 
					class HmPayload { | 
				
			||||
 | 
					    public: | 
				
			||||
 | 
					        HmPayload() {} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void setup(IApp *app, HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) { | 
				
			||||
 | 
					            mApp        = app; | 
				
			||||
 | 
					            mSys        = sys; | 
				
			||||
 | 
					            mStat       = stat; | 
				
			||||
 | 
					            mMaxRetrans = maxRetransmits; | 
				
			||||
 | 
					            mTimestamp  = timestamp; | 
				
			||||
 | 
					            for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { | 
				
			||||
 | 
					                reset(i); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					            mSerialDebug  = false; | 
				
			||||
 | 
					            mHighPrioIv   = NULL; | 
				
			||||
 | 
					            mCbAlarm      = NULL; | 
				
			||||
 | 
					            mCbPayload    = NULL; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void enableSerialDebug(bool enable) { | 
				
			||||
 | 
					            mSerialDebug = enable; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void addPayloadListener(payloadListenerType cb) { | 
				
			||||
 | 
					            mCbPayload = cb; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void addAlarmListener(alarmListenerType cb) { | 
				
			||||
 | 
					            mCbAlarm = cb; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void loop() { | 
				
			||||
 | 
					            if(NULL != mHighPrioIv) { | 
				
			||||
 | 
					                ivSend(mHighPrioIv, true); | 
				
			||||
 | 
					                mHighPrioIv = NULL; | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void zeroYieldDay(Inverter<> *iv) { | 
				
			||||
 | 
					            DPRINTLN(DBG_DEBUG, F("zeroYieldDay")); | 
				
			||||
 | 
					            record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); | 
				
			||||
 | 
					            uint8_t pos; | 
				
			||||
 | 
					            for(uint8_t ch = 0; ch < iv->channels; ch++) { | 
				
			||||
 | 
					                pos = iv->getPosByChFld(CH0, FLD_YD, rec); | 
				
			||||
 | 
					                iv->setValue(pos, rec, 0.0f); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void zeroInverterValues(Inverter<> *iv) { | 
				
			||||
 | 
					            DPRINTLN(DBG_DEBUG, F("zeroInverterValues")); | 
				
			||||
 | 
					            record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); | 
				
			||||
 | 
					            for(uint8_t ch = 0; ch <= iv->channels; ch++) { | 
				
			||||
 | 
					                uint8_t pos = 0; | 
				
			||||
 | 
					                for(uint8_t fld = 0; fld < FLD_EVT; fld++) { | 
				
			||||
 | 
					                    switch(fld) { | 
				
			||||
 | 
					                        case FLD_YD: | 
				
			||||
 | 
					                        case FLD_YT: | 
				
			||||
 | 
					                            continue; | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                    pos = iv->getPosByChFld(ch, fld, rec); | 
				
			||||
 | 
					                    iv->setValue(pos, rec, 0.0f); | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            notify(RealTimeRunData_Debug); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void ivSendHighPrio(Inverter<> *iv) { | 
				
			||||
 | 
					            mHighPrioIv = iv; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void ivSend(Inverter<> *iv, bool highPrio = false) { | 
				
			||||
 | 
					            if(!highPrio) { | 
				
			||||
 | 
					                if (mPayload[iv->id].requested) { | 
				
			||||
 | 
					                    if (!mPayload[iv->id].complete) | 
				
			||||
 | 
					                        process(false); // no retransmit
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                    if (!mPayload[iv->id].complete) { | 
				
			||||
 | 
					                        if (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId) | 
				
			||||
 | 
					                            mStat->rxFailNoAnser++; // got nothing
 | 
				
			||||
 | 
					                        else | 
				
			||||
 | 
					                            mStat->rxFail++; // got fragments but not complete response
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                        iv->setQueuedCmdFinished();  // command failed
 | 
				
			||||
 | 
					                        if (mSerialDebug) { | 
				
			||||
 | 
					                            DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout")); | 
				
			||||
 | 
					                            DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                            DBGPRINT(F("no Payload received! (retransmits: ")); | 
				
			||||
 | 
					                            DBGPRINT(String(mPayload[iv->id].retransmits)); | 
				
			||||
 | 
					                            DBGPRINTLN(F(")")); | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            reset(iv->id); | 
				
			||||
 | 
					            mPayload[iv->id].requested = true; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            yield(); | 
				
			||||
 | 
					            if (mSerialDebug) { | 
				
			||||
 | 
					                DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                DBGPRINT(F("Requesting Inv SN ")); | 
				
			||||
 | 
					                DBGPRINTLN(String(iv->config->serial.u64, HEX)); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            if (iv->getDevControlRequest()) { | 
				
			||||
 | 
					                if (mSerialDebug) { | 
				
			||||
 | 
					                    DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                    DBGPRINT(F("Devcontrol request 0x")); | 
				
			||||
 | 
					                    DBGPRINT(String(iv->devControlCmd, HEX)); | 
				
			||||
 | 
					                    DBGPRINT(F(" power limit ")); | 
				
			||||
 | 
					                    DBGPRINTLN(String(iv->powerLimit[0])); | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					                mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false); | 
				
			||||
 | 
					                mPayload[iv->id].txCmd = iv->devControlCmd; | 
				
			||||
 | 
					                //iv->clearCmdQueue();
 | 
				
			||||
 | 
					                //iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
 | 
				
			||||
 | 
					            } else { | 
				
			||||
 | 
					                uint8_t cmd = iv->getQueuedCmd(); | 
				
			||||
 | 
					                DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                DBGPRINT(F("prepareDevInformCmd 0x")); | 
				
			||||
 | 
					                DBGHEXLN(cmd); | 
				
			||||
 | 
					                mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false); | 
				
			||||
 | 
					                mPayload[iv->id].txCmd = cmd; | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void add(Inverter<> *iv, packet_t *p) { | 
				
			||||
 | 
					            if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) {  // response from get information command
 | 
				
			||||
 | 
					                mPayload[iv->id].txId = p->packet[0]; | 
				
			||||
 | 
					                DPRINTLN(DBG_DEBUG, F("Response from info request received")); | 
				
			||||
 | 
					                uint8_t *pid = &p->packet[9]; | 
				
			||||
 | 
					                if (*pid == 0x00) { | 
				
			||||
 | 
					                    DPRINTLN(DBG_DEBUG, F("fragment number zero received and ignored")); | 
				
			||||
 | 
					                } else { | 
				
			||||
 | 
					                    DPRINT(DBG_DEBUG, F("PID: 0x")); | 
				
			||||
 | 
					                    DPRINTLN(DBG_DEBUG, String(*pid, HEX)); | 
				
			||||
 | 
					                    if ((*pid & 0x7F) < MAX_PAYLOAD_ENTRIES) { | 
				
			||||
 | 
					                        memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], p->len - 11); | 
				
			||||
 | 
					                        mPayload[iv->id].len[(*pid & 0x7F) - 1] = p->len - 11; | 
				
			||||
 | 
					                        mPayload[iv->id].gotFragment = true; | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                    if ((*pid & ALL_FRAMES) == ALL_FRAMES) { | 
				
			||||
 | 
					                        // Last packet
 | 
				
			||||
 | 
					                        if (((*pid & 0x7f) > mPayload[iv->id].maxPackId) || (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId)) { | 
				
			||||
 | 
					                            mPayload[iv->id].maxPackId = (*pid & 0x7f); | 
				
			||||
 | 
					                            if (*pid > 0x81) | 
				
			||||
 | 
					                                mPayload[iv->id].lastFound = true; | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            } else if (p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES)) { // response from dev control command
 | 
				
			||||
 | 
					                DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received")); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                mPayload[iv->id].txId = p->packet[0]; | 
				
			||||
 | 
					                iv->clearDevControlRequest(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if ((p->packet[12] == ActivePowerContr) && (p->packet[13] == 0x00)) { | 
				
			||||
 | 
					                    bool ok = true; | 
				
			||||
 | 
					                    if((p->packet[10] == 0x00) && (p->packet[11] == 0x00)) | 
				
			||||
 | 
					                        mApp->setMqttPowerLimitAck(iv); | 
				
			||||
 | 
					                    else | 
				
			||||
 | 
					                        ok = false; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                    DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                    DBGPRINT(F("has ")); | 
				
			||||
 | 
					                    if(!ok) DBGPRINT(F("not ")); | 
				
			||||
 | 
					                    DBGPRINT(F("accepted power limit set point ")); | 
				
			||||
 | 
					                    DBGPRINT(String(iv->powerLimit[0])); | 
				
			||||
 | 
					                    DBGPRINT(F(" with PowerLimitControl ")); | 
				
			||||
 | 
					                    DBGPRINTLN(String(iv->powerLimit[1])); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                    iv->clearCmdQueue(); | 
				
			||||
 | 
					                    iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
 | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					                iv->devControlCmd = Init; | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void process(bool retransmit) { | 
				
			||||
 | 
					            for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { | 
				
			||||
 | 
					                Inverter<> *iv = mSys->getInverterByPos(id); | 
				
			||||
 | 
					                if (NULL == iv) | 
				
			||||
 | 
					                    continue; // skip to next inverter
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if (IV_MI == iv->ivGen) // only process HM inverters
 | 
				
			||||
 | 
					                    continue; // skip to next inverter
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if ((mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && (0 != mPayload[iv->id].txId)) { | 
				
			||||
 | 
					                    // no processing needed if txId is not 0x95
 | 
				
			||||
 | 
					                    mPayload[iv->id].complete = true; | 
				
			||||
 | 
					                    continue; // skip to next inverter
 | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if (!mPayload[iv->id].complete) { | 
				
			||||
 | 
					                    bool crcPass, pyldComplete; | 
				
			||||
 | 
					                    crcPass = build(iv->id, &pyldComplete); | 
				
			||||
 | 
					                    if (!crcPass && !pyldComplete) { // payload not complete
 | 
				
			||||
 | 
					                        if ((mPayload[iv->id].requested) && (retransmit)) { | 
				
			||||
 | 
					                            if (mPayload[iv->id].retransmits < mMaxRetrans) { | 
				
			||||
 | 
					                                mPayload[iv->id].retransmits++; | 
				
			||||
 | 
					                                if (iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm) { | 
				
			||||
 | 
					                                    // This is required to prevent retransmissions without answer.
 | 
				
			||||
 | 
					                                    DPRINTLN(DBG_INFO, F("Prevent retransmit on Restart / CleanState_LockAndAlarm...")); | 
				
			||||
 | 
					                                    mPayload[iv->id].retransmits = mMaxRetrans; | 
				
			||||
 | 
					                                } else if(iv->devControlCmd == ActivePowerContr) { | 
				
			||||
 | 
					                                    DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                                    DPRINTLN(DBG_INFO, F("retransmit power limit")); | 
				
			||||
 | 
					                                    mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true); | 
				
			||||
 | 
					                                } else { | 
				
			||||
 | 
					                                    if(false == mPayload[iv->id].gotFragment) { | 
				
			||||
 | 
					                                        /*
 | 
				
			||||
 | 
					                                        DPRINTLN(DBG_WARN, F("nothing received: Request Complete Retransmit")); | 
				
			||||
 | 
					                                        mPayload[iv->id].txCmd = iv->getQueuedCmd(); | 
				
			||||
 | 
					                                        DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd 0x") + String(mPayload[iv->id].txCmd, HEX)); | 
				
			||||
 | 
					                                        mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true); | 
				
			||||
 | 
					                                        */ | 
				
			||||
 | 
					                                        DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                                        DBGPRINTLN(F("nothing received")); | 
				
			||||
 | 
					                                        mPayload[iv->id].retransmits = mMaxRetrans; | 
				
			||||
 | 
					                                    } else { | 
				
			||||
 | 
					                                        for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) { | 
				
			||||
 | 
					                                            if (mPayload[iv->id].len[i] == 0) { | 
				
			||||
 | 
					                                                DPRINT_IVID(DBG_WARN, iv->id); | 
				
			||||
 | 
					                                                DBGPRINT(F("Frame ")); | 
				
			||||
 | 
					                                                DBGPRINT(String(i + 1)); | 
				
			||||
 | 
					                                                DBGPRINTLN(F(" missing: Request Retransmit")); | 
				
			||||
 | 
					                                                mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true); | 
				
			||||
 | 
					                                                break;  // only request retransmit one frame per loop
 | 
				
			||||
 | 
					                                            } | 
				
			||||
 | 
					                                            yield(); | 
				
			||||
 | 
					                                        } | 
				
			||||
 | 
					                                    } | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                            } | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                    } else if(!crcPass && pyldComplete) { // crc error on complete Payload
 | 
				
			||||
 | 
					                        if (mPayload[iv->id].retransmits < mMaxRetrans) { | 
				
			||||
 | 
					                            mPayload[iv->id].retransmits++; | 
				
			||||
 | 
					                            DPRINTLN(DBG_WARN, F("CRC Error: Request Complete Retransmit")); | 
				
			||||
 | 
					                            mPayload[iv->id].txCmd = iv->getQueuedCmd(); | 
				
			||||
 | 
					                            DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                            DBGPRINT(F("prepareDevInformCmd 0x")); | 
				
			||||
 | 
					                            DBGHEXLN(mPayload[iv->id].txCmd); | 
				
			||||
 | 
					                            mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true); | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                    } else {  // payload complete
 | 
				
			||||
 | 
					                        DPRINT(DBG_INFO, F("procPyld: cmd:  0x")); | 
				
			||||
 | 
					                        DBGHEXLN(mPayload[iv->id].txCmd); | 
				
			||||
 | 
					                        DPRINT(DBG_INFO, F("procPyld: txid: 0x")); | 
				
			||||
 | 
					                        DBGHEXLN(mPayload[iv->id].txId); | 
				
			||||
 | 
					                        DPRINT(DBG_DEBUG, F("procPyld: max:  ")); | 
				
			||||
 | 
					                        DPRINTLN(DBG_DEBUG, String(mPayload[iv->id].maxPackId)); | 
				
			||||
 | 
					                        record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd);  // choose the parser
 | 
				
			||||
 | 
					                        mPayload[iv->id].complete = true; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                        uint8_t payload[128]; | 
				
			||||
 | 
					                        uint8_t payloadLen = 0; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                        memset(payload, 0, 128); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                        for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i++) { | 
				
			||||
 | 
					                            memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i])); | 
				
			||||
 | 
					                            payloadLen += (mPayload[iv->id].len[i]); | 
				
			||||
 | 
					                            yield(); | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                        payloadLen -= 2; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                        if (mSerialDebug) { | 
				
			||||
 | 
					                            DPRINT(DBG_INFO, F("Payload (")); | 
				
			||||
 | 
					                            DBGPRINT(String(payloadLen)); | 
				
			||||
 | 
					                            DBGPRINT(F("): ")); | 
				
			||||
 | 
					                            mSys->Radio.dumpBuf(payload, payloadLen); | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                        if (NULL == rec) { | 
				
			||||
 | 
					                            DPRINTLN(DBG_ERROR, F("record is NULL!")); | 
				
			||||
 | 
					                        } else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) { | 
				
			||||
 | 
					                            if (mPayload[iv->id].txId == (TX_REQ_INFO + ALL_FRAMES)) | 
				
			||||
 | 
					                                mStat->rxSuccess++; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                            rec->ts = mPayload[iv->id].ts; | 
				
			||||
 | 
					                            for (uint8_t i = 0; i < rec->length; i++) { | 
				
			||||
 | 
					                                iv->addValue(i, payload, rec); | 
				
			||||
 | 
					                                yield(); | 
				
			||||
 | 
					                            } | 
				
			||||
 | 
					                            iv->doCalculations(); | 
				
			||||
 | 
					                            notify(mPayload[iv->id].txCmd); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                            if(AlarmData == mPayload[iv->id].txCmd) { | 
				
			||||
 | 
					                                uint8_t i = 0; | 
				
			||||
 | 
					                                uint16_t code; | 
				
			||||
 | 
					                                uint32_t start, end; | 
				
			||||
 | 
					                                while(1) { | 
				
			||||
 | 
					                                    code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); | 
				
			||||
 | 
					                                    if(0 == code) | 
				
			||||
 | 
					                                        break; | 
				
			||||
 | 
					                                    if (NULL != mCbAlarm) | 
				
			||||
 | 
					                                        (mCbAlarm)(code, start, end); | 
				
			||||
 | 
					                                    yield(); | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                            } | 
				
			||||
 | 
					                        } else { | 
				
			||||
 | 
					                            DPRINT(DBG_ERROR, F("plausibility check failed, expected ")); | 
				
			||||
 | 
					                            DBGPRINT(String(rec->pyldLen)); | 
				
			||||
 | 
					                            DBGPRINTLN(F(" bytes")); | 
				
			||||
 | 
					                            mStat->rxFail++; | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                        iv->setQueuedCmdFinished(); | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					                yield(); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    private: | 
				
			||||
 | 
					        void notify(uint8_t val) { | 
				
			||||
 | 
					            if(NULL != mCbPayload) | 
				
			||||
 | 
					                (mCbPayload)(val); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void notify(uint16_t code, uint32_t start, uint32_t endTime) { | 
				
			||||
 | 
					            if (NULL != mCbAlarm) | 
				
			||||
 | 
					                (mCbAlarm)(code, start, endTime); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        bool build(uint8_t id, bool *complete) { | 
				
			||||
 | 
					            DPRINTLN(DBG_VERBOSE, F("build")); | 
				
			||||
 | 
					            uint16_t crc = 0xffff, crcRcv = 0x0000; | 
				
			||||
 | 
					            if (mPayload[id].maxPackId > MAX_PAYLOAD_ENTRIES) | 
				
			||||
 | 
					                mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            // check if all fragments are there
 | 
				
			||||
 | 
					            *complete = true; | 
				
			||||
 | 
					            for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) { | 
				
			||||
 | 
					                if(mPayload[id].len[i] == 0) | 
				
			||||
 | 
					                    *complete = false; | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					            if(!*complete) | 
				
			||||
 | 
					                return false; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) { | 
				
			||||
 | 
					                if (mPayload[id].len[i] > 0) { | 
				
			||||
 | 
					                    if (i == (mPayload[id].maxPackId - 1)) { | 
				
			||||
 | 
					                        crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc); | 
				
			||||
 | 
					                        crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8) | (mPayload[id].data[i][mPayload[id].len[i] - 1]); | 
				
			||||
 | 
					                    } else | 
				
			||||
 | 
					                        crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i], crc); | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					                yield(); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            return (crc == crcRcv) ? true : false; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void reset(uint8_t id) { | 
				
			||||
 | 
					            DPRINT(DBG_INFO, "resetPayload: id: "); | 
				
			||||
 | 
					            DBGPRINTLN(String(id)); | 
				
			||||
 | 
					            memset(mPayload[id].len, 0, MAX_PAYLOAD_ENTRIES); | 
				
			||||
 | 
					            mPayload[id].txCmd       = 0; | 
				
			||||
 | 
					            mPayload[id].gotFragment = false; | 
				
			||||
 | 
					            mPayload[id].retransmits = 0; | 
				
			||||
 | 
					            mPayload[id].maxPackId   = MAX_PAYLOAD_ENTRIES; | 
				
			||||
 | 
					            mPayload[id].lastFound   = false; | 
				
			||||
 | 
					            mPayload[id].complete    = false; | 
				
			||||
 | 
					            mPayload[id].requested   = false; | 
				
			||||
 | 
					            mPayload[id].ts          = *mTimestamp; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        IApp *mApp; | 
				
			||||
 | 
					        HMSYSTEM *mSys; | 
				
			||||
 | 
					        statistics_t *mStat; | 
				
			||||
 | 
					        uint8_t mMaxRetrans; | 
				
			||||
 | 
					        uint32_t *mTimestamp; | 
				
			||||
 | 
					        invPayload_t mPayload[MAX_NUM_INVERTERS]; | 
				
			||||
 | 
					        bool mSerialDebug; | 
				
			||||
 | 
					        Inverter<> *mHighPrioIv; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        alarmListenerType mCbAlarm; | 
				
			||||
 | 
					        payloadListenerType mCbPayload; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#endif /*__HM_PAYLOAD_H__*/ | 
				
			||||
@ -0,0 +1,825 @@ | 
				
			|||||
 | 
					//-----------------------------------------------------------------------------
 | 
				
			||||
 | 
					// 2023 Ahoy, https://ahoydtu.de
 | 
				
			||||
 | 
					// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
 | 
				
			||||
 | 
					//-----------------------------------------------------------------------------
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#ifndef __MI_PAYLOAD_H__ | 
				
			||||
 | 
					#define __MI_PAYLOAD_H__ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//#include "hmInverter.h"
 | 
				
			||||
 | 
					#include "../utils/dbg.h" | 
				
			||||
 | 
					#include "../utils/crc.h" | 
				
			||||
 | 
					#include "../config/config.h" | 
				
			||||
 | 
					#include <Arduino.h> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					typedef struct { | 
				
			||||
 | 
					    uint32_t ts; | 
				
			||||
 | 
					    bool requested; | 
				
			||||
 | 
					    bool limitrequested; | 
				
			||||
 | 
					    uint8_t txCmd; | 
				
			||||
 | 
					    uint8_t len[MAX_PAYLOAD_ENTRIES]; | 
				
			||||
 | 
					    bool complete; | 
				
			||||
 | 
					    bool dataAB[3]; | 
				
			||||
 | 
					    bool stsAB[3]; | 
				
			||||
 | 
					    uint16_t sts[6]; | 
				
			||||
 | 
					    uint8_t txId; | 
				
			||||
 | 
					    uint8_t invId; | 
				
			||||
 | 
					    uint8_t retransmits; | 
				
			||||
 | 
					    //uint8_t skipfirstrepeat;
 | 
				
			||||
 | 
					    bool gotFragment; | 
				
			||||
 | 
					    /*
 | 
				
			||||
 | 
					    uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE]; | 
				
			||||
 | 
					    uint8_t maxPackId; | 
				
			||||
 | 
					    bool lastFound;*/ | 
				
			||||
 | 
					} miPayload_t; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					typedef std::function<void(uint8_t)> miPayloadListenerType; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					template<class HMSYSTEM> | 
				
			||||
 | 
					class MiPayload { | 
				
			||||
 | 
					    public: | 
				
			||||
 | 
					        MiPayload() {} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void setup(IApp *app, HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) { | 
				
			||||
 | 
					            mApp        = app; | 
				
			||||
 | 
					            mSys        = sys; | 
				
			||||
 | 
					            mStat       = stat; | 
				
			||||
 | 
					            mMaxRetrans = maxRetransmits; | 
				
			||||
 | 
					            mTimestamp  = timestamp; | 
				
			||||
 | 
					            for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { | 
				
			||||
 | 
					                reset(i, true); | 
				
			||||
 | 
					                mPayload[i].limitrequested = true; | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					            mSerialDebug  = false; | 
				
			||||
 | 
					            mHighPrioIv   = NULL; | 
				
			||||
 | 
					            mCbMiPayload  = NULL; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void enableSerialDebug(bool enable) { | 
				
			||||
 | 
					            mSerialDebug = enable; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void addPayloadListener(miPayloadListenerType cb) { | 
				
			||||
 | 
					            mCbMiPayload = cb; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void addAlarmListener(alarmListenerType cb) { | 
				
			||||
 | 
					            mCbMiAlarm = cb; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void loop() { | 
				
			||||
 | 
					            if(NULL != mHighPrioIv) { // && mHighPrioIv->ivGen == IV_MI) {
 | 
				
			||||
 | 
					                ivSend(mHighPrioIv, true); // for devcontrol commands?
 | 
				
			||||
 | 
					                mHighPrioIv = NULL; | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void ivSendHighPrio(Inverter<> *iv) { | 
				
			||||
 | 
					            mHighPrioIv = iv; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void ivSend(Inverter<> *iv, bool highPrio = false) { | 
				
			||||
 | 
					            if(!highPrio) { | 
				
			||||
 | 
					                if (mPayload[iv->id].requested) { | 
				
			||||
 | 
					                    if (!mPayload[iv->id].complete) | 
				
			||||
 | 
					                        process(false); // no retransmit
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                    if (!mPayload[iv->id].complete) { | 
				
			||||
 | 
					                        if (!mPayload[iv->id].gotFragment) | 
				
			||||
 | 
					                            mStat->rxFailNoAnser++; // got nothing
 | 
				
			||||
 | 
					                        else | 
				
			||||
 | 
					                            mStat->rxFail++; // got fragments but not complete response
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                        iv->setQueuedCmdFinished();  // command failed
 | 
				
			||||
 | 
					                        if (mSerialDebug) | 
				
			||||
 | 
					                            DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                            DBGPRINTLN(F("enqueued cmd failed/timeout")); | 
				
			||||
 | 
					                        if (mSerialDebug) { | 
				
			||||
 | 
					                            DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                            DBGPRINT(F("no Payload received! (retransmits: ")); | 
				
			||||
 | 
					                            DBGPRINT(String(mPayload[iv->id].retransmits)); | 
				
			||||
 | 
					                            DBGPRINTLN(F(")")); | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            reset(iv->id); | 
				
			||||
 | 
					            mPayload[iv->id].requested = true; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            yield(); | 
				
			||||
 | 
					            if (mSerialDebug){ | 
				
			||||
 | 
					                DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                DBGPRINT(F("Requesting Inv SN ")); | 
				
			||||
 | 
					                DBGPRINTLN(String(iv->config->serial.u64, HEX)); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            if (iv->getDevControlRequest()) { | 
				
			||||
 | 
					                if (mSerialDebug) { | 
				
			||||
 | 
					                    DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                    DBGPRINT(F("Devcontrol request 0x")); | 
				
			||||
 | 
					                    DHEX(iv->devControlCmd); | 
				
			||||
 | 
					                    DBGPRINT(F(" power limit ")); | 
				
			||||
 | 
					                    DBGPRINTLN(String(iv->powerLimit[0])); | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					                mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false, false); | 
				
			||||
 | 
					                mPayload[iv->id].txCmd = iv->devControlCmd; | 
				
			||||
 | 
					                mPayload[iv->id].limitrequested = true; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                iv->clearCmdQueue(); | 
				
			||||
 | 
					                iv->enqueCommand<InfoCommand>(SystemConfigPara); // try to read back power limit
 | 
				
			||||
 | 
					            } else { | 
				
			||||
 | 
					                uint8_t cmd = iv->getQueuedCmd(); | 
				
			||||
 | 
					                DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                DBGPRINT(F("prepareDevInformCmd 0x")); | 
				
			||||
 | 
					                DBGHEXLN(cmd); | 
				
			||||
 | 
					                uint8_t cmd2 = cmd; | 
				
			||||
 | 
					                if ( cmd == SystemConfigPara ) { //0x05 for HM-types
 | 
				
			||||
 | 
					                    if (!mPayload[iv->id].limitrequested) { // only do once at startup
 | 
				
			||||
 | 
					                        iv->setQueuedCmdFinished(); | 
				
			||||
 | 
					                        cmd = iv->getQueuedCmd(); | 
				
			||||
 | 
					                    } else { | 
				
			||||
 | 
					                        mPayload[iv->id].limitrequested = false; | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if (cmd == 0x01 || cmd == SystemConfigPara ) { //0x1 and 0x05 for HM-types
 | 
				
			||||
 | 
					                    cmd  = 0x0f;                              // for MI, these seem to make part of the  Polling the device software and hardware version number command
 | 
				
			||||
 | 
					                    cmd2 = cmd == SystemConfigPara ? 0x01 : 0x00;  //perhaps we can only try to get second frame?
 | 
				
			||||
 | 
					                    mSys->Radio.sendCmdPacket(iv->radioId.u64, cmd, cmd2, false); | 
				
			||||
 | 
					                } else { | 
				
			||||
 | 
					                    mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd2, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd); | 
				
			||||
 | 
					                }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                mPayload[iv->id].txCmd = cmd; | 
				
			||||
 | 
					                if (iv->type == INV_TYPE_1CH || iv->type == INV_TYPE_2CH) { | 
				
			||||
 | 
					                    mPayload[iv->id].dataAB[CH1] = false; | 
				
			||||
 | 
					                    mPayload[iv->id].stsAB[CH1] = false; | 
				
			||||
 | 
					                    mPayload[iv->id].dataAB[CH0] = false; | 
				
			||||
 | 
					                    mPayload[iv->id].stsAB[CH0] = false; | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if (iv->type == INV_TYPE_2CH) { | 
				
			||||
 | 
					                    mPayload[iv->id].dataAB[CH2] = false; | 
				
			||||
 | 
					                    mPayload[iv->id].stsAB[CH2] = false; | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void add(Inverter<> *iv, packet_t *p) { | 
				
			||||
 | 
					            //DPRINTLN(DBG_INFO, F("MI got data [0]=") + String(p->packet[0], HEX));
 | 
				
			||||
 | 
					            if (p->packet[0] == (0x08 + ALL_FRAMES)) { // 0x88; MI status response to 0x09
 | 
				
			||||
 | 
					                miStsDecode(iv, p); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            else if (p->packet[0] == (0x11 + SINGLE_FRAME)) { // 0x92; MI status response to 0x11
 | 
				
			||||
 | 
					                miStsDecode(iv, p, CH2); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            else if ( p->packet[0] == 0x09 + ALL_FRAMES || | 
				
			||||
 | 
					                        p->packet[0] == 0x11 + ALL_FRAMES || | 
				
			||||
 | 
					                        ( p->packet[0] >= (0x36 + ALL_FRAMES) && p->packet[0] < (0x39 + SINGLE_FRAME) | 
				
			||||
 | 
					                          && mPayload[iv->id].txCmd != 0x0f) ) { // small MI or MI 1500 data responses to 0x09, 0x11, 0x36, 0x37, 0x38 and 0x39
 | 
				
			||||
 | 
					                mPayload[iv->id].txId = p->packet[0]; | 
				
			||||
 | 
					                miDataDecode(iv,p); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            else if (p->packet[0] == ( 0x0f + ALL_FRAMES)) { | 
				
			||||
 | 
					                // MI response from get hardware information request
 | 
				
			||||
 | 
					                record_t<> *rec = iv->getRecordStruct(InverterDevInform_All);  // choose the record structure
 | 
				
			||||
 | 
					                rec->ts = mPayload[iv->id].ts; | 
				
			||||
 | 
					                mPayload[iv->id].gotFragment = true; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/*
 | 
				
			||||
 | 
					 Polling the device software and hardware version number command | 
				
			||||
 | 
					 start byte	Command word	 routing address				 target address				 User data	 check	 end byte | 
				
			||||
 | 
					 byte[0]	 byte[1]	 byte[2]	 byte[3]	 byte[4]	 byte[5]	 byte[6]	 byte[7]	 byte[8]	 byte[9]	 byte[10]	 byte[11]	 byte[12] | 
				
			||||
 | 
					 0x7e	 0x0f	 xx	 xx	 xx	 xx	 YY	 YY	 YY	 YY	 0x00	 CRC	 0x7f | 
				
			||||
 | 
					 Command Receipt - First Frame | 
				
			||||
 | 
					 start byte	Command word	 target address				 routing address				 Multi-frame marking	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 check	 end byte | 
				
			||||
 | 
					 byte[0]	 byte[1]	 byte[2]	 byte[3]	 byte[4]	 byte[5]	 byte[6]	 byte[7]	 byte[8]	 byte[9]	 byte[10]	 byte[11]	 byte[12]	 byte[13]	 byte[14]	 byte[15]	 byte[16]	 byte[17]	 byte[18]	 byte[19]	 byte[20]	 byte[21]	 byte[22]	 byte[23]	 byte[24]	 byte[25]	 byte[26]	 byte[27]	 byte[28] | 
				
			||||
 | 
					 0x7e	 0x8f	 YY	 YY	 YY	 YY	 xx	 xx	 xx	 xx	 0x00	 USFWBuild_VER		 APPFWBuild_VER		 APPFWBuild_YYYY		 APPFWBuild_MMDD		 APPFWBuild_HHMM		 APPFW_PN				 HW_VER		 CRC	 0x7f | 
				
			||||
 | 
					 Command Receipt - Second Frame | 
				
			||||
 | 
					 start byte	Command word	 target address				 routing address				 Multi-frame marking	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 check	 end byte | 
				
			||||
 | 
					 byte[0]	 byte[1]	 byte[2]	 byte[3]	 byte[4]	 byte[5]	 byte[6]	 byte[7]	 byte[8]	 byte[9]	 byte[10]	 byte[11]	 byte[12]	 byte[13]	 byte[14]	 byte[15]	 byte[16]	 byte[17]	 byte[18]	 byte[19]	 byte[20]	 byte[21]	 byte[22]	 byte[23]	 byte[24]	 byte[25]	 byte[26]	 byte[27]	 byte[28] | 
				
			||||
 | 
					 0x7e	 0x8f	 YY	 YY	 YY	 YY	 xx	 xx	 xx	 xx	 0x01	 HW_PN				 HW_FB_TLmValue		 HW_FB_ReSPRT		 HW_GridSamp_ResValule		 HW_ECapValue		 Matching_APPFW_PN				 CRC	 0x7f | 
				
			||||
 | 
					 Command receipt - third frame | 
				
			||||
 | 
					 start byte	Command word	 target address				 routing address				 Multi-frame marking	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 User data	 check	 end byte | 
				
			||||
 | 
					 byte[0]	 byte[1]	 byte[2]	 byte[3]	 byte[4]	 byte[5]	 byte[6]	 byte[7]	 byte[8]	 byte[9]	 byte[10]	 byte[11]	 byte[12]	 byte[13]	 byte[14]	 byte[15]	 byte[16]	 byte[15]	 byte[16]	 byte[17]	 byte[18] | 
				
			||||
 | 
					 0x7e	 0x8f	 YY	 YY	 YY	 YY	 xx	 xx	 xx	 xx	 0x12	 APPFW_MINVER		 HWInfoAddr		 PNInfoCRC_gusv		 PNInfoCRC_gusv		 CRC	 0x7f | 
				
			||||
 | 
					*/ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/*
 | 
				
			||||
 | 
					case InverterDevInform_All: | 
				
			||||
 | 
					                    rec->length  = (uint8_t)(HMINFO_LIST_LEN); | 
				
			||||
 | 
					                    rec->assign  = (byteAssign_t *)InfoAssignment; | 
				
			||||
 | 
					                    rec->pyldLen = HMINFO_PAYLOAD_LEN; | 
				
			||||
 | 
					                    break; | 
				
			||||
 | 
					const byteAssign_t InfoAssignment[] = { | 
				
			||||
 | 
					    { FLD_FW_VERSION,           UNIT_NONE,   CH0,  0, 2, 1 }, | 
				
			||||
 | 
					    { FLD_FW_BUILD_YEAR,        UNIT_NONE,   CH0,  2, 2, 1 }, | 
				
			||||
 | 
					    { FLD_FW_BUILD_MONTH_DAY,   UNIT_NONE,   CH0,  4, 2, 1 }, | 
				
			||||
 | 
					    { FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE,   CH0,  6, 2, 1 }, | 
				
			||||
 | 
					    { FLD_HW_ID,                UNIT_NONE,   CH0,  8, 2, 1 } | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					*/ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if ( p->packet[9] == 0x00 ) {//first frame
 | 
				
			||||
 | 
					                    //FLD_FW_VERSION
 | 
				
			||||
 | 
					                    for (uint8_t i = 0; i < 5; i++) { | 
				
			||||
 | 
					                        iv->setValue(i, rec, (float) ((p->packet[(12+2*i)] << 8) + p->packet[(13+2*i)])/1); | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                    iv->isConnected = true; | 
				
			||||
 | 
					                    if(mSerialDebug) { | 
				
			||||
 | 
					                        DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                        DPRINT(DBG_INFO,F("HW_VER is ")); | 
				
			||||
 | 
					                        DBGPRINTLN(String((p->packet[24] << 8) + p->packet[25])); | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                    /*iv->setQueuedCmdFinished();
 | 
				
			||||
 | 
					                    mSys->Radio.sendCmdPacket(iv->radioId.u64, 0x0f, 0x01, false);*/ | 
				
			||||
 | 
					                } else if ( p->packet[9] == 0x01 || p->packet[9] == 0x10 ) {//second frame for MI, 3rd gen. answers in 0x10
 | 
				
			||||
 | 
					                    DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                    if ( p->packet[9] == 0x01 ) { | 
				
			||||
 | 
					                        DBGPRINTLN(F("got 2nd frame (hw info)")); | 
				
			||||
 | 
					                    } else { | 
				
			||||
 | 
					                        DBGPRINTLN(F("3rd gen. inverter!"));           // see table in OpenDTU code, DevInfoParser.cpp devInfo[]
 | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                    // xlsx: HW_ECapValue is total energy?!? (data coll. inst. #154)
 | 
				
			||||
 | 
					                    DPRINT(DBG_INFO,F("HW_PartNo ")); | 
				
			||||
 | 
					                    DBGPRINTLN(String((uint32_t) (((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13])); | 
				
			||||
 | 
					                    //DBGPRINTLN(String((p->packet[12] << 8) + p->packet[13]));
 | 
				
			||||
 | 
					                    if ( p->packet[9] == 0x01 ) { | 
				
			||||
 | 
					                        iv->setValue(iv->getPosByChFld(0, FLD_YT, rec), rec, (float) ((p->packet[20] << 8) + p->packet[21])/1); | 
				
			||||
 | 
					                        if(mSerialDebug) { | 
				
			||||
 | 
					                            DPRINT(DBG_INFO,F("HW_ECapValue ")); | 
				
			||||
 | 
					                            DBGPRINTLN(String((p->packet[20] << 8) + p->packet[21])); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                            DPRINT(DBG_INFO,F("HW_FB_TLmValue ")); | 
				
			||||
 | 
					                            DBGPRINTLN(String((p->packet[14] << 8) + p->packet[15])); | 
				
			||||
 | 
					                            DPRINT(DBG_INFO,F("HW_FB_ReSPRT ")); | 
				
			||||
 | 
					                            DBGPRINTLN(String((p->packet[16] << 8) + p->packet[17])); | 
				
			||||
 | 
					                            DPRINT(DBG_INFO,F("HW_GridSamp_ResValule ")); | 
				
			||||
 | 
					                            DBGPRINTLN(String((p->packet[18] << 8) + p->packet[19])); | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                } else if ( p->packet[9] == 0x12 ) {//3rd frame
 | 
				
			||||
 | 
					                    DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                    DBGPRINTLN(F("got 3rd frame (hw info)")); | 
				
			||||
 | 
					                    iv->setQueuedCmdFinished(); | 
				
			||||
 | 
					                    mStat->rxSuccess++; | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            } else if ( p->packet[0] == (TX_REQ_INFO + ALL_FRAMES) // response from get information command
 | 
				
			||||
 | 
					                     || (p->packet[0] == 0xB6 && mPayload[iv->id].txCmd != 0x36)) {                   // strange short response from MI-1500 3rd gen; might be missleading!
 | 
				
			||||
 | 
					                // atm, we just do nothing else than print out what we got...
 | 
				
			||||
 | 
					                // for decoding see xls- Data collection instructions - #147ff
 | 
				
			||||
 | 
					                //mPayload[iv->id].txId = p->packet[0];
 | 
				
			||||
 | 
					                DPRINTLN(DBG_DEBUG, F("Response from info request received")); | 
				
			||||
 | 
					                uint8_t *pid = &p->packet[9]; | 
				
			||||
 | 
					                if (*pid == 0x00) { | 
				
			||||
 | 
					                    DPRINT(DBG_DEBUG, F("fragment number zero received")); | 
				
			||||
 | 
					                    iv->setQueuedCmdFinished(); | 
				
			||||
 | 
					                } else if (p->packet[9] == 0x81) { // might need some additional check, as this is only ment for short answers!
 | 
				
			||||
 | 
					                    DPRINT_IVID(DBG_WARN, iv->id); | 
				
			||||
 | 
					                    DBGPRINTLN(F("seems to use 3rd gen. protocol - switching ivGen!")); | 
				
			||||
 | 
					                    iv->ivGen = IV_HM; | 
				
			||||
 | 
					                    iv->setQueuedCmdFinished(); | 
				
			||||
 | 
					                    iv->clearCmdQueue(); | 
				
			||||
 | 
					                    //DPRINTLN(DBG_DEBUG, "PID: 0x" + String(*pid, HEX));
 | 
				
			||||
 | 
					                    /* (old else-tree)
 | 
				
			||||
 | 
					                    if ((*pid & 0x7F) < MAX_PAYLOAD_ENTRIES) {^ | 
				
			||||
 | 
					                        memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], p->len - 11); | 
				
			||||
 | 
					                        mPayload[iv->id].len[(*pid & 0x7F) - 1] = p->len - 11; | 
				
			||||
 | 
					                        mPayload[iv->id].gotFragment = true; | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                    if ((*pid & ALL_FRAMES) == ALL_FRAMES) { | 
				
			||||
 | 
					                        // Last packet
 | 
				
			||||
 | 
					                        if (((*pid & 0x7f) > mPayload[iv->id].maxPackId) || (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId)) { | 
				
			||||
 | 
					                            mPayload[iv->id].maxPackId = (*pid & 0x7f); | 
				
			||||
 | 
					                            if (*pid > 0x81) | 
				
			||||
 | 
					                                mPayload[iv->id].lastFound = true; | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                    }*/ | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            //}
 | 
				
			||||
 | 
					            } else if (p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES )       // response from dev control command
 | 
				
			||||
 | 
					                    || p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES -1)) {  // response from DRED instruction
 | 
				
			||||
 | 
					                DPRINT_IVID(DBG_DEBUG, iv->id); | 
				
			||||
 | 
					                DBGPRINTLN(F("Response from devcontrol request received")); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                mPayload[iv->id].txId = p->packet[0]; | 
				
			||||
 | 
					                iv->clearDevControlRequest(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if ((p->packet[9] == 0x5a) && (p->packet[10] == 0x5a)) { | 
				
			||||
 | 
					                    mApp->setMqttPowerLimitAck(iv); | 
				
			||||
 | 
					                    DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                    DBGPRINT(F("has accepted power limit set point ")); | 
				
			||||
 | 
					                    DBGPRINT(String(iv->powerLimit[0])); | 
				
			||||
 | 
					                    DBGPRINT(F(" with PowerLimitControl ")); | 
				
			||||
 | 
					                    DBGPRINTLN(String(iv->powerLimit[1])); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                    iv->clearCmdQueue(); | 
				
			||||
 | 
					                    iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
 | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					                iv->devControlCmd = Init; | 
				
			||||
 | 
					            } else {  // some other response; copied from hmPayload:process; might not be correct to do that here!!!
 | 
				
			||||
 | 
					                DPRINT(DBG_INFO, F("procPyld: cmd:  0x")); | 
				
			||||
 | 
					                DBGHEXLN(mPayload[iv->id].txCmd); | 
				
			||||
 | 
					                DPRINT(DBG_INFO, F("procPyld: txid: 0x")); | 
				
			||||
 | 
					                DBGHEXLN(mPayload[iv->id].txId); | 
				
			||||
 | 
					                //DPRINT(DBG_DEBUG, F("procPyld: max:  "));
 | 
				
			||||
 | 
					                //DBGPRINTLN(String(mPayload[iv->id].maxPackId));
 | 
				
			||||
 | 
					                record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd);  // choose the parser
 | 
				
			||||
 | 
					                mPayload[iv->id].complete = true; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                uint8_t payload[128]; | 
				
			||||
 | 
					                uint8_t payloadLen = 0; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                memset(payload, 0, 128); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                /*for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i++) {
 | 
				
			||||
 | 
					                    memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i])); | 
				
			||||
 | 
					                    payloadLen += (mPayload[iv->id].len[i]); | 
				
			||||
 | 
					                    yield(); | 
				
			||||
 | 
					                }*/ | 
				
			||||
 | 
					                payloadLen -= 2; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if (mSerialDebug) { | 
				
			||||
 | 
					                    DPRINT(DBG_INFO, F("Payload (") + String(payloadLen) + "): "); | 
				
			||||
 | 
					                    mSys->Radio.dumpBuf(payload, payloadLen); | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if (NULL == rec) { | 
				
			||||
 | 
					                    DPRINTLN(DBG_ERROR, F("record is NULL!")); | 
				
			||||
 | 
					                } else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) { | 
				
			||||
 | 
					                    if (mPayload[iv->id].txId == (TX_REQ_INFO + ALL_FRAMES)) | 
				
			||||
 | 
					                        mStat->rxSuccess++; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                    rec->ts = mPayload[iv->id].ts; | 
				
			||||
 | 
					                    for (uint8_t i = 0; i < rec->length; i++) { | 
				
			||||
 | 
					                        iv->addValue(i, payload, rec); | 
				
			||||
 | 
					                        yield(); | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                    iv->doCalculations(); | 
				
			||||
 | 
					                    notify(mPayload[iv->id].txCmd); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                    if(AlarmData == mPayload[iv->id].txCmd) { | 
				
			||||
 | 
					                        uint8_t i = 0; | 
				
			||||
 | 
					                        uint16_t code; | 
				
			||||
 | 
					                        uint32_t start, end; | 
				
			||||
 | 
					                        while(1) { | 
				
			||||
 | 
					                            code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); | 
				
			||||
 | 
					                            if(0 == code) | 
				
			||||
 | 
					                                break; | 
				
			||||
 | 
					                            if (NULL != mCbMiAlarm) | 
				
			||||
 | 
					                                (mCbMiAlarm)(code, start, end); | 
				
			||||
 | 
					                            yield(); | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                } else { | 
				
			||||
 | 
					                    DPRINTLN(DBG_ERROR, F("plausibility check failed, expected ") + String(rec->pyldLen) + F(" bytes")); | 
				
			||||
 | 
					                    mStat->rxFail++; | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                iv->setQueuedCmdFinished(); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void process(bool retransmit) { | 
				
			||||
 | 
					            for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { | 
				
			||||
 | 
					                Inverter<> *iv = mSys->getInverterByPos(id); | 
				
			||||
 | 
					                if (NULL == iv) | 
				
			||||
 | 
					                    continue; // skip to next inverter
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if (IV_HM == iv->ivGen) // only process MI inverters
 | 
				
			||||
 | 
					                    continue; // skip to next inverter
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if ( !mPayload[iv->id].complete && | 
				
			||||
 | 
					                    (mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && | 
				
			||||
 | 
					                    (mPayload[iv->id].txId <  (0x36 + ALL_FRAMES)) && | 
				
			||||
 | 
					                    (mPayload[iv->id].txId >  (0x39 + ALL_FRAMES)) && | 
				
			||||
 | 
					                    (mPayload[iv->id].txId != (0x09 + ALL_FRAMES)) && | 
				
			||||
 | 
					                    (mPayload[iv->id].txId != (0x11 + ALL_FRAMES)) && | 
				
			||||
 | 
					                    (mPayload[iv->id].txId != (0x88)) && | 
				
			||||
 | 
					                    (mPayload[iv->id].txId != (0x92)) && | 
				
			||||
 | 
					                    (mPayload[iv->id].txId != 0 )) { | 
				
			||||
 | 
					                    // no processing needed if txId is not one of 0x95, 0x88, 0x89, 0x91, 0x92 or resonse to 0x36ff
 | 
				
			||||
 | 
					                    mPayload[iv->id].complete = true; | 
				
			||||
 | 
					                    continue; // skip to next inverter
 | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                //delayed next message?
 | 
				
			||||
 | 
					                //mPayload[iv->id].skipfirstrepeat++;
 | 
				
			||||
 | 
					                /*if (mPayload[iv->id].skipfirstrepeat) {
 | 
				
			||||
 | 
					                    mPayload[iv->id].skipfirstrepeat = 0; //reset counter
 | 
				
			||||
 | 
					                    continue; // skip to next inverter
 | 
				
			||||
 | 
					                }*/ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if (!mPayload[iv->id].complete) { | 
				
			||||
 | 
					                    //DPRINTLN(DBG_INFO, F("Pyld incompl code")); //info for testing only
 | 
				
			||||
 | 
					                    bool crcPass, pyldComplete; | 
				
			||||
 | 
					                    crcPass = build(iv->id, &pyldComplete); | 
				
			||||
 | 
					                    if (!crcPass && !pyldComplete) { // payload not complete
 | 
				
			||||
 | 
					                        if ((mPayload[iv->id].requested) && (retransmit)) { | 
				
			||||
 | 
					                            if (iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm) { | 
				
			||||
 | 
					                                // This is required to prevent retransmissions without answer.
 | 
				
			||||
 | 
					                                DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                                DBGPRINTLN(F("Prevent retransmit on Restart / CleanState_LockAndAlarm...")); | 
				
			||||
 | 
					                                mPayload[iv->id].retransmits = mMaxRetrans; | 
				
			||||
 | 
					                            } else if(iv->devControlCmd == ActivePowerContr) { | 
				
			||||
 | 
					                                DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                                DBGPRINTLN(F("retransmit power limit")); | 
				
			||||
 | 
					                                mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true, false); | 
				
			||||
 | 
					                            } else { | 
				
			||||
 | 
					                                uint8_t cmd = mPayload[iv->id].txCmd; | 
				
			||||
 | 
					                                if (mPayload[iv->id].retransmits < mMaxRetrans) { | 
				
			||||
 | 
					                                    mPayload[iv->id].retransmits++; | 
				
			||||
 | 
					                                    if( !mPayload[iv->id].gotFragment ) { | 
				
			||||
 | 
					                                        DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                                        DBGPRINTLN(F("nothing received")); | 
				
			||||
 | 
					                                        mPayload[iv->id].retransmits = mMaxRetrans; | 
				
			||||
 | 
					                                    } else if ( cmd == 0x0f ) { | 
				
			||||
 | 
					                                        //hard/firmware request
 | 
				
			||||
 | 
					                                        mSys->Radio.sendCmdPacket(iv->radioId.u64, 0x0f, 0x00, true); | 
				
			||||
 | 
					                                        //iv->setQueuedCmdFinished();
 | 
				
			||||
 | 
					                                        //cmd = iv->getQueuedCmd();
 | 
				
			||||
 | 
					                                    } else { | 
				
			||||
 | 
					                                        bool change = false; | 
				
			||||
 | 
					                                        if ( cmd >= 0x36 && cmd < 0x39 ) { // MI-1500 Data command
 | 
				
			||||
 | 
					                                            //cmd++; // just request the next channel
 | 
				
			||||
 | 
					                                            //change = true;
 | 
				
			||||
 | 
					                                        } else if ( cmd == 0x09 ) {//MI single or dual channel device
 | 
				
			||||
 | 
					                                            if ( mPayload[iv->id].dataAB[CH1] && iv->type == INV_TYPE_2CH  ) { | 
				
			||||
 | 
					                                                if (!mPayload[iv->id].stsAB[CH1] && mPayload[iv->id].retransmits<2) {} | 
				
			||||
 | 
					                                                    //first try to get missing sts for first channel a second time
 | 
				
			||||
 | 
					                                                else if (!mPayload[iv->id].stsAB[CH2] || !mPayload[iv->id].dataAB[CH2] ) { | 
				
			||||
 | 
					                                                    cmd = 0x11; | 
				
			||||
 | 
					                                                    change = true; | 
				
			||||
 | 
					                                                    mPayload[iv->id].retransmits = 0; //reset counter
 | 
				
			||||
 | 
					                                                } | 
				
			||||
 | 
					                                            } | 
				
			||||
 | 
					                                        } else if ( cmd == 0x11) { | 
				
			||||
 | 
					                                            if ( mPayload[iv->id].dataAB[CH2] ) { // data + status ch2 are there?
 | 
				
			||||
 | 
					                                                if (mPayload[iv->id].stsAB[CH2] && (!mPayload[iv->id].stsAB[CH1] || !mPayload[iv->id].dataAB[CH1])) { | 
				
			||||
 | 
					                                                    cmd = 0x09; | 
				
			||||
 | 
					                                                    change = true; | 
				
			||||
 | 
					                                                } | 
				
			||||
 | 
					                                            } | 
				
			||||
 | 
					                                        } | 
				
			||||
 | 
					                                        DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                                        if (change) { | 
				
			||||
 | 
					                                            DBGPRINT(F("next request is")); | 
				
			||||
 | 
					                                            //mPayload[iv->id].skipfirstrepeat = 0;
 | 
				
			||||
 | 
					                                            mPayload[iv->id].txCmd = cmd; | 
				
			||||
 | 
					                                        } else { | 
				
			||||
 | 
					                                            DBGPRINT(F("sth.")); | 
				
			||||
 | 
					                                            DBGPRINT(F(" missing: Request Retransmit")); | 
				
			||||
 | 
					                                        } | 
				
			||||
 | 
					                                        DBGPRINT(F(" 0x")); | 
				
			||||
 | 
					                                        DBGHEXLN(cmd); | 
				
			||||
 | 
					                                        //mSys->Radio.sendCmdPacket(iv->radioId.u64, cmd, cmd, true);
 | 
				
			||||
 | 
					                                        mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, true, cmd); | 
				
			||||
 | 
					                                        yield(); | 
				
			||||
 | 
					                                    } | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                            } | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                    } else if(!crcPass && pyldComplete) { // crc error on complete Payload
 | 
				
			||||
 | 
					                        if (mPayload[iv->id].retransmits < mMaxRetrans) { | 
				
			||||
 | 
					                            mPayload[iv->id].retransmits++; | 
				
			||||
 | 
					                            DPRINT_IVID(DBG_WARN, iv->id); | 
				
			||||
 | 
					                            DBGPRINTLN(F("CRC Error: Request Complete Retransmit")); | 
				
			||||
 | 
					                            mPayload[iv->id].txCmd = iv->getQueuedCmd(); | 
				
			||||
 | 
					                            DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                            DBGPRINT(F("prepareDevInformCmd 0x")); | 
				
			||||
 | 
					                            DBGHEXLN(mPayload[iv->id].txCmd); | 
				
			||||
 | 
					                            mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true); | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                    /*else {  // payload complete
 | 
				
			||||
 | 
					                        //This tree is not really tested, most likely it's not truly complete....
 | 
				
			||||
 | 
					                        DPRINTLN(DBG_INFO, F("procPyld: cmd:  0x") + String(mPayload[iv->id].txCmd, HEX)); | 
				
			||||
 | 
					                        DPRINTLN(DBG_INFO, F("procPyld: txid: 0x") + String(mPayload[iv->id].txId, HEX)); | 
				
			||||
 | 
					                        //DPRINTLN(DBG_DEBUG, F("procPyld: max:  ") + String(mPayload[iv->id].maxPackId));
 | 
				
			||||
 | 
					                        //record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd);  // choose the parser
 | 
				
			||||
 | 
					                        //uint8_t payload[128];
 | 
				
			||||
 | 
					                        //uint8_t payloadLen = 0;
 | 
				
			||||
 | 
					                        //memset(payload, 0, 128);
 | 
				
			||||
 | 
					                        //for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i++) {
 | 
				
			||||
 | 
					                        //    memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i]));
 | 
				
			||||
 | 
					                        //    payloadLen += (mPayload[iv->id].len[i]);
 | 
				
			||||
 | 
					                        //    yield();
 | 
				
			||||
 | 
					                        //}
 | 
				
			||||
 | 
					                        //payloadLen -= 2;
 | 
				
			||||
 | 
					                        //if (mSerialDebug) {
 | 
				
			||||
 | 
					                        //    DPRINT(DBG_INFO, F("Payload (") + String(payloadLen) + "): ");
 | 
				
			||||
 | 
					                        //    mSys->Radio.dumpBuf(payload, payloadLen);
 | 
				
			||||
 | 
					                        //}
 | 
				
			||||
 | 
					                        //if (NULL == rec) {
 | 
				
			||||
 | 
					                        //    DPRINTLN(DBG_ERROR, F("record is NULL!"));
 | 
				
			||||
 | 
					                        //} else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) {
 | 
				
			||||
 | 
					                        //    if (mPayload[iv->id].txId == (TX_REQ_INFO + ALL_FRAMES))
 | 
				
			||||
 | 
					                        //        mStat->rxSuccess++;
 | 
				
			||||
 | 
					                        //    rec->ts = mPayload[iv->id].ts;
 | 
				
			||||
 | 
					                        //    for (uint8_t i = 0; i < rec->length; i++) {
 | 
				
			||||
 | 
					                        //        iv->addValue(i, payload, rec);
 | 
				
			||||
 | 
					                        //        yield();
 | 
				
			||||
 | 
					                        //    }
 | 
				
			||||
 | 
					                        //    iv->doCalculations();
 | 
				
			||||
 | 
					                        //    notify(mPayload[iv->id].txCmd);
 | 
				
			||||
 | 
					                        //    if(AlarmData == mPayload[iv->id].txCmd) {
 | 
				
			||||
 | 
					                        //        uint8_t i = 0;
 | 
				
			||||
 | 
					                        //        uint16_t code;
 | 
				
			||||
 | 
					                        //        uint32_t start, end;
 | 
				
			||||
 | 
					                        //        while(1) {
 | 
				
			||||
 | 
					                        //            code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end);
 | 
				
			||||
 | 
					                        //            if(0 == code)
 | 
				
			||||
 | 
					                        //                break;
 | 
				
			||||
 | 
					                        //            if (NULL != mCbAlarm)
 | 
				
			||||
 | 
					                        //                (mCbAlarm)(code, start, end);
 | 
				
			||||
 | 
					                        //            yield();
 | 
				
			||||
 | 
					                        //        }
 | 
				
			||||
 | 
					                        //    }
 | 
				
			||||
 | 
					                        //} else {
 | 
				
			||||
 | 
					                        //    DPRINTLN(DBG_ERROR, F("plausibility check failed, expected ") + String(rec->pyldLen) + F(" bytes"));
 | 
				
			||||
 | 
					                        //    mStat->rxFail++;
 | 
				
			||||
 | 
					                        //}
 | 
				
			||||
 | 
					                        //iv->setQueuedCmdFinished();
 | 
				
			||||
 | 
					                    //}*/
 | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					                yield(); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    private: | 
				
			||||
 | 
					        void notify(uint8_t val) { | 
				
			||||
 | 
					            if(NULL != mCbMiPayload) | 
				
			||||
 | 
					                (mCbMiPayload)(val); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void miStsDecode(Inverter<> *iv, packet_t *p, uint8_t stschan = CH1) { | 
				
			||||
 | 
					            //DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") status msg 0x") + String(p->packet[0], HEX));
 | 
				
			||||
 | 
					            record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);  // choose the record structure
 | 
				
			||||
 | 
					            rec->ts = mPayload[iv->id].ts; | 
				
			||||
 | 
					            mPayload[iv->id].gotFragment = true; | 
				
			||||
 | 
					            mPayload[iv->id].txId = p->packet[0]; | 
				
			||||
 | 
					            miStsConsolidate(iv, stschan, rec, p->packet[10], p->packet[12], p->packet[9], p->packet[11]); | 
				
			||||
 | 
					            mPayload[iv->id].stsAB[stschan] = true; | 
				
			||||
 | 
					            if (mPayload[iv->id].stsAB[CH1] && mPayload[iv->id].stsAB[CH2]) | 
				
			||||
 | 
					                mPayload[iv->id].stsAB[CH0] = true; | 
				
			||||
 | 
					            //mPayload[iv->id].skipfirstrepeat = 1;
 | 
				
			||||
 | 
					            if (mPayload[iv->id].stsAB[CH0] && mPayload[iv->id].dataAB[CH0] && !mPayload[iv->id].complete) { | 
				
			||||
 | 
					                miComplete(iv); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void miStsConsolidate(Inverter<> *iv, uint8_t stschan,  record_t<> *rec, uint8_t uState, uint8_t uEnum, uint8_t lState = 0, uint8_t lEnum = 0) { | 
				
			||||
 | 
					            //uint8_t status  = (p->packet[11] << 8) + p->packet[12];
 | 
				
			||||
 | 
					            uint16_t status = 3; // regular status for MI, change to 1 later?
 | 
				
			||||
 | 
					            if ( uState == 2 ) { | 
				
			||||
 | 
					                status = 5050 + stschan; //first approach, needs review!
 | 
				
			||||
 | 
					                if (lState) | 
				
			||||
 | 
					                    status +=  lState*10; | 
				
			||||
 | 
					            } else if ( uState > 3 ) { | 
				
			||||
 | 
					                status = uState*1000 + uEnum*10; | 
				
			||||
 | 
					                if (lState) | 
				
			||||
 | 
					                    status +=  lState*100; //needs review, esp. for 4ch-8310 state!
 | 
				
			||||
 | 
					                //if (lEnum)
 | 
				
			||||
 | 
					                status +=  lEnum; | 
				
			||||
 | 
					                if (uEnum < 6) { | 
				
			||||
 | 
					                    status += stschan; | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					                if (status == 8000) | 
				
			||||
 | 
					                    status = 8310;       //trick?
 | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            uint16_t prntsts = status == 3 ? 1 : status; | 
				
			||||
 | 
					            if ( status != mPayload[iv->id].sts[stschan] ) { //sth.'s changed?
 | 
				
			||||
 | 
					                mPayload[iv->id].sts[stschan] = status; | 
				
			||||
 | 
					                DPRINT(DBG_WARN, F("Status change for CH")); | 
				
			||||
 | 
					                DBGPRINT(String(stschan)); DBGPRINT(F(" (")); | 
				
			||||
 | 
					                DBGPRINT(String(prntsts)); DBGPRINT(F("): ")); | 
				
			||||
 | 
					                DBGPRINTLN(iv->getAlarmStr(prntsts)); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            if ( !mPayload[iv->id].sts[0] || prntsts < mPayload[iv->id].sts[0] ) { | 
				
			||||
 | 
					                mPayload[iv->id].sts[0] = prntsts; | 
				
			||||
 | 
					                iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, prntsts); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){ | 
				
			||||
 | 
					                iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]; // seems there's no status per channel in 3rd gen. models?!?
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                DBGPRINT(F("alarm ID incremented to ")); | 
				
			||||
 | 
					                DBGPRINTLN(String(iv->alarmMesIndex)); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					            /*if(AlarmData == mPayload[iv->id].txCmd) {
 | 
				
			||||
 | 
					                                uint8_t i = 0; | 
				
			||||
 | 
					                                uint16_t code; | 
				
			||||
 | 
					                                uint32_t start, end; | 
				
			||||
 | 
					                                while(1) { | 
				
			||||
 | 
					                                    code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); | 
				
			||||
 | 
					                                    if(0 == code) | 
				
			||||
 | 
					                                        break; | 
				
			||||
 | 
					                                    if (NULL != mCbAlarm) | 
				
			||||
 | 
					                                        (mCbAlarm)(code, start, end); | 
				
			||||
 | 
					                                    yield(); | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                            }*/ | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void miDataDecode(Inverter<> *iv, packet_t *p) { | 
				
			||||
 | 
					            record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);  // choose the parser
 | 
				
			||||
 | 
					            rec->ts = mPayload[iv->id].ts; | 
				
			||||
 | 
					            mPayload[iv->id].gotFragment = true; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            uint8_t datachan = ( p->packet[0] == 0x89 || p->packet[0] == (0x36 + ALL_FRAMES) ) ? CH1 : | 
				
			||||
 | 
					                           ( p->packet[0] == 0x91 || p->packet[0] == (0x37 + ALL_FRAMES) ) ? CH2 : | 
				
			||||
 | 
					                           p->packet[0] == (0x38 + ALL_FRAMES) ? CH3 : | 
				
			||||
 | 
					                           CH4; | 
				
			||||
 | 
					            //DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") data msg 0x") + String(p->packet[0], HEX) + F(" channel ") + datachan);
 | 
				
			||||
 | 
					            // count in RF_communication_protocol.xlsx is with offset = -1
 | 
				
			||||
 | 
					            iv->setValue(iv->getPosByChFld(datachan, FLD_UDC, rec), rec, (float)((p->packet[9] << 8) + p->packet[10])/10); | 
				
			||||
 | 
					            yield(); | 
				
			||||
 | 
					            iv->setValue(iv->getPosByChFld(datachan, FLD_IDC, rec), rec, (float)((p->packet[11] << 8) + p->packet[12])/10); | 
				
			||||
 | 
					            yield(); | 
				
			||||
 | 
					            iv->setValue(iv->getPosByChFld(0, FLD_UAC, rec), rec, (float)((p->packet[13] << 8) + p->packet[14])/10); | 
				
			||||
 | 
					            yield(); | 
				
			||||
 | 
					            iv->setValue(iv->getPosByChFld(0, FLD_F, rec), rec, (float) ((p->packet[15] << 8) + p->packet[16])/100); | 
				
			||||
 | 
					            iv->setValue(iv->getPosByChFld(datachan, FLD_PDC, rec), rec, (float)((p->packet[17] << 8) + p->packet[18])/10); | 
				
			||||
 | 
					            yield(); | 
				
			||||
 | 
					            iv->setValue(iv->getPosByChFld(datachan, FLD_YD, rec), rec, (float)((p->packet[19] << 8) + p->packet[20])/1); | 
				
			||||
 | 
					            yield(); | 
				
			||||
 | 
					            iv->setValue(iv->getPosByChFld(0, FLD_T, rec), rec, (float) ((int16_t)(p->packet[21] << 8) + p->packet[22])/10); | 
				
			||||
 | 
					            iv->setValue(iv->getPosByChFld(0, FLD_IRR, rec), rec, (float) (calcIrradiation(iv, datachan))); | 
				
			||||
 | 
					            //AC Power is missing; we may have to calculate, as no respective data is in payload
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            if ( datachan < 3 ) { | 
				
			||||
 | 
					                mPayload[iv->id].dataAB[datachan] = true; | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					            if ( !mPayload[iv->id].dataAB[CH0] && mPayload[iv->id].dataAB[CH2] && mPayload[iv->id].dataAB[CH2] ) { | 
				
			||||
 | 
					                mPayload[iv->id].dataAB[CH0] = true; | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            if (p->packet[0] >= (0x36 + ALL_FRAMES) ) { | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                /*For MI1500:
 | 
				
			||||
 | 
					                if (MI1500) { | 
				
			||||
 | 
					                  STAT = (uint8_t)(p->packet[25] ); | 
				
			||||
 | 
					                  FCNT = (uint8_t)(p->packet[26]); | 
				
			||||
 | 
					                  FCODE = (uint8_t)(p->packet[27]); | 
				
			||||
 | 
					                }*/ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                /*uint16_t status = (uint8_t)(p->packet[23]);
 | 
				
			||||
 | 
					                mPayload[iv->id].sts[datachan] = status; | 
				
			||||
 | 
					                if ( !mPayload[iv->id].sts[0] || status < mPayload[iv->id].sts[0]) { | 
				
			||||
 | 
					                    mPayload[iv->id].sts[0] = status; | 
				
			||||
 | 
					                    iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, status); | 
				
			||||
 | 
					                }*/ | 
				
			||||
 | 
					                miStsConsolidate(iv, datachan, rec, p->packet[23], p->packet[24]); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if (p->packet[0] < (0x39 + ALL_FRAMES) ) { | 
				
			||||
 | 
					                    /*uint8_t cmd = p->packet[0] - ALL_FRAMES + 1;
 | 
				
			||||
 | 
					                    mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd); | 
				
			||||
 | 
					                    mPayload[iv->id].txCmd = cmd;*/ | 
				
			||||
 | 
					                    mPayload[iv->id].txCmd++; | 
				
			||||
 | 
					                    if (mPayload[iv->id].retransmits) | 
				
			||||
 | 
					                        mPayload[iv->id].retransmits--; // reserve retransmissions for each response
 | 
				
			||||
 | 
					                    mPayload[iv->id].complete = false; | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                else if (p->packet[0] == (0x39 + ALL_FRAMES) ) { | 
				
			||||
 | 
					                    /*uint8_t cmd = p->packet[0] - ALL_FRAMES + 1;
 | 
				
			||||
 | 
					                    mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd); | 
				
			||||
 | 
					                    mPayload[iv->id].txCmd = cmd;*/ | 
				
			||||
 | 
					                    mPayload[iv->id].complete = true; | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                /*if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){
 | 
				
			||||
 | 
					                    iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                    DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					                    DBGPRINT_TXT(TXT_INCRALM); | 
				
			||||
 | 
					                    DBGPRINTLN(String(iv->alarmMesIndex)); | 
				
			||||
 | 
					                }*/ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            if ( mPayload[iv->id].complete ||  //4ch device
 | 
				
			||||
 | 
					                 (iv->type != INV_TYPE_4CH     //other devices
 | 
				
			||||
 | 
					                 && mPayload[iv->id].dataAB[CH0] | 
				
			||||
 | 
					                 && mPayload[iv->id].stsAB[CH0])) { | 
				
			||||
 | 
					                     miComplete(iv); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/*
 | 
				
			||||
 | 
					                            if(AlarmData == mPayload[iv->id].txCmd) { | 
				
			||||
 | 
					                                uint8_t i = 0; | 
				
			||||
 | 
					                                uint16_t code; | 
				
			||||
 | 
					                                uint32_t start, end; | 
				
			||||
 | 
					                                while(1) { | 
				
			||||
 | 
					                                    code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); | 
				
			||||
 | 
					                                    if(0 == code) | 
				
			||||
 | 
					                                        break; | 
				
			||||
 | 
					                                    if (NULL != mCbMiAlarm) | 
				
			||||
 | 
					                                        (mCbAlarm)(code, start, end); | 
				
			||||
 | 
					                                    yield(); | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                            }*/ | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void miComplete(Inverter<> *iv) { | 
				
			||||
 | 
					            if (mPayload[iv->id].complete) | 
				
			||||
 | 
					                return; //if we got second message as well in repreated attempt
 | 
				
			||||
 | 
					            mPayload[iv->id].complete = true; // For 2 CH devices, this might be too short...
 | 
				
			||||
 | 
					            DPRINT_IVID(DBG_INFO, iv->id); | 
				
			||||
 | 
					            DBGPRINTLN(F("got all msgs")); | 
				
			||||
 | 
					            record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); | 
				
			||||
 | 
					            iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, calcYieldDayCh0(iv,0)); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            //preliminary AC calculation...
 | 
				
			||||
 | 
					            float ac_pow = 0; | 
				
			||||
 | 
					            for(uint8_t i = 1; i <= iv->channels; i++) { | 
				
			||||
 | 
					                if (mPayload[iv->id].sts[i] == 3) { | 
				
			||||
 | 
					                    uint8_t pos = iv->getPosByChFld(i, FLD_PDC, rec); | 
				
			||||
 | 
					                    ac_pow += iv->getValue(pos, rec); | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					            ac_pow = (int) (ac_pow*9.5); | 
				
			||||
 | 
					            iv->setValue(iv->getPosByChFld(0, FLD_PAC, rec), rec, (float) ac_pow/10); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            iv->doCalculations(); | 
				
			||||
 | 
					            iv->setQueuedCmdFinished(); | 
				
			||||
 | 
					            mStat->rxSuccess++; | 
				
			||||
 | 
					            yield(); | 
				
			||||
 | 
					            notify(mPayload[iv->id].txCmd); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        bool build(uint8_t id, bool *complete) { | 
				
			||||
 | 
					            DPRINTLN(DBG_VERBOSE, F("build")); | 
				
			||||
 | 
					            // check if all messages are there
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            *complete = mPayload[id].complete; | 
				
			||||
 | 
					            uint8_t txCmd = mPayload[id].txCmd; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            if(!*complete) { | 
				
			||||
 | 
					                DPRINTLN(DBG_VERBOSE, F("incomlete, txCmd is 0x") + String(txCmd, HEX)); | 
				
			||||
 | 
					                //DBGHEXLN(txCmd);
 | 
				
			||||
 | 
					                if (txCmd == 0x09 || txCmd == 0x11 || (txCmd >= 0x36 && txCmd <= 0x39)) | 
				
			||||
 | 
					                    return false; | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            return true; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void reset(uint8_t id, bool clrSts = false) { | 
				
			||||
 | 
					            DPRINT_IVID(DBG_INFO, id); | 
				
			||||
 | 
					            DBGPRINTLN(F("resetPayload")); | 
				
			||||
 | 
					            memset(mPayload[id].len, 0, MAX_PAYLOAD_ENTRIES); | 
				
			||||
 | 
					            mPayload[id].gotFragment = false; | 
				
			||||
 | 
					            /*mPayload[id].maxPackId   = MAX_PAYLOAD_ENTRIES;
 | 
				
			||||
 | 
					            mPayload[id].lastFound   = false;*/ | 
				
			||||
 | 
					            mPayload[id].retransmits = 0; | 
				
			||||
 | 
					            mPayload[id].complete    = false; | 
				
			||||
 | 
					            mPayload[id].dataAB[CH0] = true; //required for 1CH and 2CH devices
 | 
				
			||||
 | 
					            mPayload[id].dataAB[CH1] = true; //required for 1CH and 2CH devices
 | 
				
			||||
 | 
					            mPayload[id].dataAB[CH2] = true; //only required for 2CH devices
 | 
				
			||||
 | 
					            mPayload[id].stsAB[CH0]  = true; //required for 1CH and 2CH devices
 | 
				
			||||
 | 
					            mPayload[id].stsAB[CH1]  = true; //required for 1CH and 2CH devices
 | 
				
			||||
 | 
					            mPayload[id].stsAB[CH2]  = true; //only required for 2CH devices
 | 
				
			||||
 | 
					            mPayload[id].txCmd       = 0; | 
				
			||||
 | 
					            //mPayload[id].skipfirstrepeat   = 0;
 | 
				
			||||
 | 
					            mPayload[id].requested   = false; | 
				
			||||
 | 
					            mPayload[id].ts          = *mTimestamp; | 
				
			||||
 | 
					            mPayload[id].sts[0]      = 0; | 
				
			||||
 | 
					            if (clrSts) {                    // only clear channel states at startup
 | 
				
			||||
 | 
					                mPayload[id].sts[CH1]    = 0; | 
				
			||||
 | 
					                mPayload[id].sts[CH2]    = 0; | 
				
			||||
 | 
					                mPayload[id].sts[CH3]    = 0; | 
				
			||||
 | 
					                mPayload[id].sts[CH4]    = 0; | 
				
			||||
 | 
					                mPayload[id].sts[5]      = 0; //remember last summarized state
 | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        IApp *mApp; | 
				
			||||
 | 
					        HMSYSTEM *mSys; | 
				
			||||
 | 
					        statistics_t *mStat; | 
				
			||||
 | 
					        uint8_t mMaxRetrans; | 
				
			||||
 | 
					        uint32_t *mTimestamp; | 
				
			||||
 | 
					        miPayload_t mPayload[MAX_NUM_INVERTERS]; | 
				
			||||
 | 
					        bool mSerialDebug; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        Inverter<> *mHighPrioIv; | 
				
			||||
 | 
					        alarmListenerType mCbMiAlarm; | 
				
			||||
 | 
					        payloadListenerType mCbMiPayload; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#endif /*__MI_PAYLOAD_H__*/ | 
				
			||||
@ -1,251 +0,0 @@ | 
				
			|||||
//-----------------------------------------------------------------------------
 | 
					 | 
				
			||||
// 2022 Ahoy, https://ahoydtu.de
 | 
					 | 
				
			||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
 | 
					 | 
				
			||||
//-----------------------------------------------------------------------------
 | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#ifndef __PAYLOAD_H__ | 
					 | 
				
			||||
#define __PAYLOAD_H__ | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#include "../utils/dbg.h" | 
					 | 
				
			||||
#include "../utils/crc.h" | 
					 | 
				
			||||
#include "../utils/handler.h" | 
					 | 
				
			||||
#include "../config/config.h" | 
					 | 
				
			||||
#include <Arduino.h> | 
					 | 
				
			||||
 | 
					 | 
				
			||||
typedef struct { | 
					 | 
				
			||||
    uint8_t txCmd; | 
					 | 
				
			||||
    uint8_t txId; | 
					 | 
				
			||||
    uint8_t invId; | 
					 | 
				
			||||
    uint32_t ts; | 
					 | 
				
			||||
    uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE]; | 
					 | 
				
			||||
    uint8_t len[MAX_PAYLOAD_ENTRIES]; | 
					 | 
				
			||||
    bool complete; | 
					 | 
				
			||||
    uint8_t maxPackId; | 
					 | 
				
			||||
    uint8_t retransmits; | 
					 | 
				
			||||
    bool requested; | 
					 | 
				
			||||
} invPayload_t; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
 | 
					 | 
				
			||||
typedef std::function<void(uint8_t)> payloadListenerType; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
 | 
					 | 
				
			||||
template<class HMSYSTEM> | 
					 | 
				
			||||
class Payload : public Handler<payloadListenerType> { | 
					 | 
				
			||||
    public: | 
					 | 
				
			||||
        Payload() : Handler() {} | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void setup(HMSYSTEM *sys) { | 
					 | 
				
			||||
            mSys = sys; | 
					 | 
				
			||||
            memset(mPayload, 0, (MAX_NUM_INVERTERS * sizeof(invPayload_t))); | 
					 | 
				
			||||
            mLastPacketId = 0x00; | 
					 | 
				
			||||
            mSerialDebug = false; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void enableSerialDebug(bool enable) { | 
					 | 
				
			||||
            mSerialDebug = enable; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        bool isComplete(Inverter<> *iv) { | 
					 | 
				
			||||
            return mPayload[iv->id].complete; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        uint8_t getMaxPacketId(Inverter<> *iv) { | 
					 | 
				
			||||
            return mPayload[iv->id].maxPackId; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        uint8_t getRetransmits(Inverter<> *iv) { | 
					 | 
				
			||||
            return mPayload[iv->id].retransmits; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        uint32_t getTs(Inverter<> *iv) { | 
					 | 
				
			||||
            return mPayload[iv->id].ts; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void request(Inverter<> *iv) { | 
					 | 
				
			||||
            mPayload[iv->id].requested = true; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void setTxCmd(Inverter<> *iv, uint8_t cmd) { | 
					 | 
				
			||||
            mPayload[iv->id].txCmd = cmd; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void notify(uint8_t val) { | 
					 | 
				
			||||
            for(typename std::list<payloadListenerType>::iterator it = mList.begin(); it != mList.end(); ++it) { | 
					 | 
				
			||||
               (*it)(val); | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void add(packet_t *p, uint8_t len) { | 
					 | 
				
			||||
            Inverter<> *iv = mSys->findInverter(&p->packet[1]); | 
					 | 
				
			||||
            if ((NULL != iv) && (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES))) {  // response from get information command
 | 
					 | 
				
			||||
                mPayload[iv->id].txId = p->packet[0]; | 
					 | 
				
			||||
                DPRINTLN(DBG_DEBUG, F("Response from info request received")); | 
					 | 
				
			||||
                uint8_t *pid = &p->packet[9]; | 
					 | 
				
			||||
                if (*pid == 0x00) { | 
					 | 
				
			||||
                    DPRINT(DBG_DEBUG, F("fragment number zero received and ignored")); | 
					 | 
				
			||||
                } else { | 
					 | 
				
			||||
                    DPRINTLN(DBG_DEBUG, "PID: 0x" + String(*pid, HEX)); | 
					 | 
				
			||||
                    if ((*pid & 0x7F) < 5) { | 
					 | 
				
			||||
                        memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], len - 11); | 
					 | 
				
			||||
                        mPayload[iv->id].len[(*pid & 0x7F) - 1] = len - 11; | 
					 | 
				
			||||
                    } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                    if ((*pid & ALL_FRAMES) == ALL_FRAMES) { | 
					 | 
				
			||||
                        // Last packet
 | 
					 | 
				
			||||
                        if ((*pid & 0x7f) > mPayload[iv->id].maxPackId) { | 
					 | 
				
			||||
                            mPayload[iv->id].maxPackId = (*pid & 0x7f); | 
					 | 
				
			||||
                            if (*pid > 0x81) | 
					 | 
				
			||||
                                mLastPacketId = *pid; | 
					 | 
				
			||||
                        } | 
					 | 
				
			||||
                    } | 
					 | 
				
			||||
                } | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            if ((NULL != iv) && (p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES))) { // response from dev control command
 | 
					 | 
				
			||||
                DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received")); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                mPayload[iv->id].txId = p->packet[0]; | 
					 | 
				
			||||
                iv->devControlRequest = false; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                if ((p->packet[12] == ActivePowerContr) && (p->packet[13] == 0x00)) { | 
					 | 
				
			||||
                    String msg = (p->packet[10] == 0x00 && p->packet[11] == 0x00) ? "" : "NOT "; | 
					 | 
				
			||||
                    DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has ") + msg + F("accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1])); | 
					 | 
				
			||||
                } | 
					 | 
				
			||||
                iv->devControlCmd = Init; | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        bool build(uint8_t id) { | 
					 | 
				
			||||
            DPRINTLN(DBG_VERBOSE, F("build")); | 
					 | 
				
			||||
            uint16_t crc = 0xffff, crcRcv = 0x0000; | 
					 | 
				
			||||
            if (mPayload[id].maxPackId > MAX_PAYLOAD_ENTRIES) | 
					 | 
				
			||||
                mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
            for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) { | 
					 | 
				
			||||
                if (mPayload[id].len[i] > 0) { | 
					 | 
				
			||||
                    if (i == (mPayload[id].maxPackId - 1)) { | 
					 | 
				
			||||
                        crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc); | 
					 | 
				
			||||
                        crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8) | (mPayload[id].data[i][mPayload[id].len[i] - 1]); | 
					 | 
				
			||||
                    } else | 
					 | 
				
			||||
                        crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i], crc); | 
					 | 
				
			||||
                } | 
					 | 
				
			||||
                yield(); | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
            return (crc == crcRcv) ? true : false; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void process(bool retransmit, uint8_t maxRetransmits, statistics_t *stat) { | 
					 | 
				
			||||
            for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { | 
					 | 
				
			||||
                Inverter<> *iv = mSys->getInverterByPos(id); | 
					 | 
				
			||||
                if (NULL == iv) | 
					 | 
				
			||||
                    continue; // skip to next inverter
 | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                if ((mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && (0 != mPayload[iv->id].txId)) { | 
					 | 
				
			||||
                    // no processing needed if txId is not 0x95
 | 
					 | 
				
			||||
                    // DPRINTLN(DBG_INFO, F("processPayload - set complete, txId: ") + String(mPayload[iv->id].txId, HEX));
 | 
					 | 
				
			||||
                    mPayload[iv->id].complete = true; | 
					 | 
				
			||||
                } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                if (!mPayload[iv->id].complete) { | 
					 | 
				
			||||
                    if (!build(iv->id)) { // payload not complete
 | 
					 | 
				
			||||
                        if ((mPayload[iv->id].requested) && (retransmit)) { | 
					 | 
				
			||||
                            if (iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm) { | 
					 | 
				
			||||
                                // This is required to prevent retransmissions without answer.
 | 
					 | 
				
			||||
                                DPRINTLN(DBG_INFO, F("Prevent retransmit on Restart / CleanState_LockAndAlarm...")); | 
					 | 
				
			||||
                                mPayload[iv->id].retransmits = maxRetransmits; | 
					 | 
				
			||||
                            } else { | 
					 | 
				
			||||
                                if (mPayload[iv->id].retransmits < maxRetransmits) { | 
					 | 
				
			||||
                                    mPayload[iv->id].retransmits++; | 
					 | 
				
			||||
                                    if (mPayload[iv->id].maxPackId != 0) { | 
					 | 
				
			||||
                                        for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) { | 
					 | 
				
			||||
                                            if (mPayload[iv->id].len[i] == 0) { | 
					 | 
				
			||||
                                                DPRINTLN(DBG_WARN, F("while retrieving data: Frame ") + String(i + 1) + F(" missing: Request Retransmit")); | 
					 | 
				
			||||
                                                mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true); | 
					 | 
				
			||||
                                                break;  // only retransmit one frame per loop
 | 
					 | 
				
			||||
                                            } | 
					 | 
				
			||||
                                            yield(); | 
					 | 
				
			||||
                                        } | 
					 | 
				
			||||
                                    } else { | 
					 | 
				
			||||
                                        DPRINTLN(DBG_WARN, F("while retrieving data: last frame missing: Request Retransmit")); | 
					 | 
				
			||||
                                        if (0x00 != mLastPacketId) | 
					 | 
				
			||||
                                            mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, mLastPacketId, true); | 
					 | 
				
			||||
                                        else { | 
					 | 
				
			||||
                                            mPayload[iv->id].txCmd = iv->getQueuedCmd(); | 
					 | 
				
			||||
                                            DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket")); | 
					 | 
				
			||||
                                            mSys->Radio.sendTimePacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex); | 
					 | 
				
			||||
                                        } | 
					 | 
				
			||||
                                    } | 
					 | 
				
			||||
                                    mSys->Radio.switchRxCh(100); | 
					 | 
				
			||||
                                } | 
					 | 
				
			||||
                            } | 
					 | 
				
			||||
                        } | 
					 | 
				
			||||
                    } else {  // payload complete
 | 
					 | 
				
			||||
                        DPRINTLN(DBG_INFO, F("procPyld: cmd:  ") + String(mPayload[iv->id].txCmd)); | 
					 | 
				
			||||
                        DPRINTLN(DBG_INFO, F("procPyld: txid: 0x") + String(mPayload[iv->id].txId, HEX)); | 
					 | 
				
			||||
                        DPRINTLN(DBG_DEBUG, F("procPyld: max:  ") + String(mPayload[iv->id].maxPackId)); | 
					 | 
				
			||||
                        record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd);  // choose the parser
 | 
					 | 
				
			||||
                        mPayload[iv->id].complete = true; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                        uint8_t payload[128]; | 
					 | 
				
			||||
                        uint8_t payloadLen = 0; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                        memset(payload, 0, 128); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                        for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i++) { | 
					 | 
				
			||||
                            memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i])); | 
					 | 
				
			||||
                            payloadLen += (mPayload[iv->id].len[i]); | 
					 | 
				
			||||
                            yield(); | 
					 | 
				
			||||
                        } | 
					 | 
				
			||||
                        payloadLen -= 2; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                        if (mSerialDebug) { | 
					 | 
				
			||||
                            DPRINT(DBG_INFO, F("Payload (") + String(payloadLen) + "): "); | 
					 | 
				
			||||
                            mSys->Radio.dumpBuf(NULL, payload, payloadLen); | 
					 | 
				
			||||
                        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                        if (NULL == rec) { | 
					 | 
				
			||||
                            DPRINTLN(DBG_ERROR, F("record is NULL!")); | 
					 | 
				
			||||
                        } else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) { | 
					 | 
				
			||||
                            if (mPayload[iv->id].txId == (TX_REQ_INFO + 0x80)) | 
					 | 
				
			||||
                                stat->rxSuccess++; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                            rec->ts = mPayload[iv->id].ts; | 
					 | 
				
			||||
                            for (uint8_t i = 0; i < rec->length; i++) { | 
					 | 
				
			||||
                                iv->addValue(i, payload, rec); | 
					 | 
				
			||||
                                yield(); | 
					 | 
				
			||||
                            } | 
					 | 
				
			||||
                            iv->doCalculations(); | 
					 | 
				
			||||
                            notify(mPayload[iv->id].txCmd); | 
					 | 
				
			||||
                        } else { | 
					 | 
				
			||||
                            DPRINTLN(DBG_ERROR, F("plausibility check failed, expected ") + String(rec->pyldLen) + F(" bytes")); | 
					 | 
				
			||||
                            stat->rxFail++; | 
					 | 
				
			||||
                        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                        iv->setQueuedCmdFinished(); | 
					 | 
				
			||||
                    } | 
					 | 
				
			||||
                } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                yield(); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void reset(Inverter<> *iv, uint32_t utcTs) { | 
					 | 
				
			||||
            DPRINTLN(DBG_INFO, "resetPayload: id: " + String(iv->id)); | 
					 | 
				
			||||
            memset(mPayload[iv->id].len, 0, MAX_PAYLOAD_ENTRIES); | 
					 | 
				
			||||
            mPayload[iv->id].txCmd = 0; | 
					 | 
				
			||||
            mPayload[iv->id].retransmits = 0; | 
					 | 
				
			||||
            mPayload[iv->id].maxPackId = 0; | 
					 | 
				
			||||
            mPayload[iv->id].complete = false; | 
					 | 
				
			||||
            mPayload[iv->id].requested = false; | 
					 | 
				
			||||
            mPayload[iv->id].ts = utcTs; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
    private: | 
					 | 
				
			||||
        HMSYSTEM *mSys; | 
					 | 
				
			||||
        invPayload_t mPayload[MAX_NUM_INVERTERS]; | 
					 | 
				
			||||
        uint8_t mLastPacketId; | 
					 | 
				
			||||
        bool mSerialDebug; | 
					 | 
				
			||||
}; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#endif /*__PAYLOAD_H_*/ | 
					 | 
				
			||||
@ -0,0 +1,114 @@ | 
				
			|||||
 | 
					#ifndef __DISPLAY__ | 
				
			||||
 | 
					#define __DISPLAY__ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include <Timezone.h> | 
				
			||||
 | 
					#include <U8g2lib.h> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include "../../hm/hmSystem.h" | 
				
			||||
 | 
					#include "../../utils/helper.h" | 
				
			||||
 | 
					#include "Display_Mono.h" | 
				
			||||
 | 
					#include "Display_ePaper.h" | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					template <class HMSYSTEM> | 
				
			||||
 | 
					class Display { | 
				
			||||
 | 
					   public: | 
				
			||||
 | 
					        Display() {} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, const char *version) { | 
				
			||||
 | 
					            mCfg = cfg; | 
				
			||||
 | 
					            mSys = sys; | 
				
			||||
 | 
					            mUtcTs = utcTs; | 
				
			||||
 | 
					            mNewPayload = false; | 
				
			||||
 | 
					            mLoopCnt = 0; | 
				
			||||
 | 
					            mVersion = version; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            if (mCfg->type == 0) | 
				
			||||
 | 
					                return; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            if ((0 < mCfg->type) && (mCfg->type < 10)) { | 
				
			||||
 | 
					                mMono.config(mCfg->pwrSaveAtIvOffline, mCfg->pxShift, mCfg->contrast); | 
				
			||||
 | 
					                mMono.init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, 0xff, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion); | 
				
			||||
 | 
					            } else if (mCfg->type >= 10) { | 
				
			||||
 | 
					                #if defined(ESP32) | 
				
			||||
 | 
					                mRefreshCycle = 0; | 
				
			||||
 | 
					                mEpaper.config(mCfg->rot); | 
				
			||||
 | 
					                mEpaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion); | 
				
			||||
 | 
					                #endif | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void payloadEventListener(uint8_t cmd) { | 
				
			||||
 | 
					            mNewPayload = true; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        void tickerSecond() { | 
				
			||||
 | 
					            mMono.loop(); | 
				
			||||
 | 
					            if (mNewPayload || ((++mLoopCnt % 10) == 0)) { | 
				
			||||
 | 
					                mNewPayload = false; | 
				
			||||
 | 
					                mLoopCnt = 0; | 
				
			||||
 | 
					                DataScreen(); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    private: | 
				
			||||
 | 
					        void DataScreen() { | 
				
			||||
 | 
					            if (mCfg->type == 0) | 
				
			||||
 | 
					                return; | 
				
			||||
 | 
					            if (*mUtcTs == 0) | 
				
			||||
 | 
					                return; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            float totalPower = 0; | 
				
			||||
 | 
					            float totalYieldDay = 0; | 
				
			||||
 | 
					            float totalYieldTotal = 0; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            uint8_t isprod = 0; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            Inverter<> *iv; | 
				
			||||
 | 
					            record_t<> *rec; | 
				
			||||
 | 
					            for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { | 
				
			||||
 | 
					                iv = mSys->getInverterByPos(i); | 
				
			||||
 | 
					                rec = iv->getRecordStruct(RealTimeRunData_Debug); | 
				
			||||
 | 
					                if (iv == NULL) | 
				
			||||
 | 
					                    continue; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                if (iv->isProducing(*mUtcTs)) | 
				
			||||
 | 
					                    isprod++; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec); | 
				
			||||
 | 
					                totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec); | 
				
			||||
 | 
					                totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            if ((0 < mCfg->type) && (mCfg->type < 10)) { | 
				
			||||
 | 
					                mMono.disp(totalPower, totalYieldDay, totalYieldTotal, isprod); | 
				
			||||
 | 
					            } else if (mCfg->type >= 10) { | 
				
			||||
 | 
					                #if defined(ESP32) | 
				
			||||
 | 
					                mEpaper.loop(totalPower, totalYieldDay, totalYieldTotal, isprod); | 
				
			||||
 | 
					                mRefreshCycle++; | 
				
			||||
 | 
					                #endif | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            #if defined(ESP32) | 
				
			||||
 | 
					            if (mRefreshCycle > 480) { | 
				
			||||
 | 
					                mEpaper.fullRefresh(); | 
				
			||||
 | 
					                mRefreshCycle = 0; | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					            #endif | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        // private member variables
 | 
				
			||||
 | 
					        bool mNewPayload; | 
				
			||||
 | 
					        uint8_t mLoopCnt; | 
				
			||||
 | 
					        uint32_t *mUtcTs; | 
				
			||||
 | 
					        const char *mVersion; | 
				
			||||
 | 
					        display_t *mCfg; | 
				
			||||
 | 
					        HMSYSTEM *mSys; | 
				
			||||
 | 
					        uint16_t mRefreshCycle; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        #if defined(ESP32) | 
				
			||||
 | 
					        DisplayEPaper mEpaper; | 
				
			||||
 | 
					        #endif | 
				
			||||
 | 
					        DisplayMono mMono; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#endif /*__DISPLAY__*/ | 
				
			||||
@ -0,0 +1,157 @@ | 
				
			|||||
 | 
					// SPDX-License-Identifier: GPL-2.0-or-later
 | 
				
			||||
 | 
					#include "Display_Mono.h" | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#ifdef ESP8266 | 
				
			||||
 | 
					    #include <ESP8266WiFi.h> | 
				
			||||
 | 
					#elif defined(ESP32) | 
				
			||||
 | 
					    #include <WiFi.h> | 
				
			||||
 | 
					#endif | 
				
			||||
 | 
					#include "../../utils/helper.h" | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//#ifdef U8X8_HAVE_HW_SPI
 | 
				
			||||
 | 
					//#include <SPI.h>
 | 
				
			||||
 | 
					//#endif
 | 
				
			||||
 | 
					//#ifdef U8X8_HAVE_HW_I2C
 | 
				
			||||
 | 
					//#include <Wire.h>
 | 
				
			||||
 | 
					//#endif
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					DisplayMono::DisplayMono() { | 
				
			||||
 | 
					    mEnPowerSafe = true; | 
				
			||||
 | 
					    mEnScreenSaver = true; | 
				
			||||
 | 
					    mLuminance = 60; | 
				
			||||
 | 
					    _dispY = 0; | 
				
			||||
 | 
					    mTimeout = DISP_DEFAULT_TIMEOUT;  // interval at which to power save (milliseconds)
 | 
				
			||||
 | 
					    mUtcTs = NULL; | 
				
			||||
 | 
					    mType = 0; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void DisplayMono::init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char* version) { | 
				
			||||
 | 
					    if ((0 < type) && (type < 4)) { | 
				
			||||
 | 
					        u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); | 
				
			||||
 | 
					        mType = type; | 
				
			||||
 | 
					        switch(type) { | 
				
			||||
 | 
					            case 1: | 
				
			||||
 | 
					                mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, reset, clock, data); | 
				
			||||
 | 
					                break; | 
				
			||||
 | 
					            default: | 
				
			||||
 | 
					            case 2: | 
				
			||||
 | 
					                mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, reset, clock, data); | 
				
			||||
 | 
					                break; | 
				
			||||
 | 
					            case 3: | 
				
			||||
 | 
					                mDisplay = new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, clock, data, cs, dc, reset); | 
				
			||||
 | 
					                break; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        mUtcTs = utcTs; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        mDisplay->begin(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        mIsLarge = (mDisplay->getWidth() > 120); | 
				
			||||
 | 
					        calcLineHeights(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        mDisplay->clearBuffer(); | 
				
			||||
 | 
					        if (3 != mType) | 
				
			||||
 | 
					            mDisplay->setContrast(mLuminance); | 
				
			||||
 | 
					        printText("AHOY!", 0, 35); | 
				
			||||
 | 
					        printText("ahoydtu.de", 2, 20); | 
				
			||||
 | 
					        printText(version, 3, 46); | 
				
			||||
 | 
					        mDisplay->sendBuffer(); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void DisplayMono::config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { | 
				
			||||
 | 
					    mEnPowerSafe = enPowerSafe; | 
				
			||||
 | 
					    mEnScreenSaver = enScreenSaver; | 
				
			||||
 | 
					    mLuminance = lum; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void DisplayMono::loop(void) { | 
				
			||||
 | 
					    if (mEnPowerSafe) | 
				
			||||
 | 
					        if(mTimeout != 0) | 
				
			||||
 | 
					           mTimeout--; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void DisplayMono::disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    mDisplay->clearBuffer(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    // set Contrast of the Display to raise the lifetime
 | 
				
			||||
 | 
					    if (3 != mType) | 
				
			||||
 | 
					        mDisplay->setContrast(mLuminance); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    if ((totalPower > 0) && (isprod > 0)) { | 
				
			||||
 | 
					        mTimeout = DISP_DEFAULT_TIMEOUT; | 
				
			||||
 | 
					        mDisplay->setPowerSave(false); | 
				
			||||
 | 
					        if (totalPower > 999) { | 
				
			||||
 | 
					            snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); | 
				
			||||
 | 
					        } else { | 
				
			||||
 | 
					            snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					        printText(_fmtText, 0); | 
				
			||||
 | 
					    } else { | 
				
			||||
 | 
					        printText("offline", 0, 25); | 
				
			||||
 | 
					        // check if it's time to enter power saving mode
 | 
				
			||||
 | 
					        if (mTimeout == 0) | 
				
			||||
 | 
					            mDisplay->setPowerSave(mEnPowerSafe); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    snprintf(_fmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); | 
				
			||||
 | 
					    printText(_fmtText, 1); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    snprintf(_fmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); | 
				
			||||
 | 
					    printText(_fmtText, 2); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    IPAddress ip = WiFi.localIP(); | 
				
			||||
 | 
					    if (!(_mExtra % 10) && (ip)) { | 
				
			||||
 | 
					        printText(ip.toString().c_str(), 3); | 
				
			||||
 | 
					    } else if (!(_mExtra % 5)) { | 
				
			||||
 | 
					        snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", isprod); | 
				
			||||
 | 
					        printText(_fmtText, 3); | 
				
			||||
 | 
					    } else { | 
				
			||||
 | 
					        if(mIsLarge && (NULL != mUtcTs)) | 
				
			||||
 | 
					            printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); | 
				
			||||
 | 
					        else | 
				
			||||
 | 
					            printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    mDisplay->sendBuffer(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    _dispY = 0; | 
				
			||||
 | 
					    _mExtra++; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void DisplayMono::calcLineHeights() { | 
				
			||||
 | 
					    uint8_t yOff = 0; | 
				
			||||
 | 
					    for (uint8_t i = 0; i < 4; i++) { | 
				
			||||
 | 
					        setFont(i); | 
				
			||||
 | 
					        yOff += (mDisplay->getMaxCharHeight()); | 
				
			||||
 | 
					        mLineOffsets[i] = yOff; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					inline void DisplayMono::setFont(uint8_t line) { | 
				
			||||
 | 
					    switch (line) { | 
				
			||||
 | 
					        case 0: | 
				
			||||
 | 
					            mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB14_tr : u8g2_font_logisoso16_tr); | 
				
			||||
 | 
					            break; | 
				
			||||
 | 
					        case 3: | 
				
			||||
 | 
					            mDisplay->setFont(u8g2_font_5x8_tr); | 
				
			||||
 | 
					            break; | 
				
			||||
 | 
					        default: | 
				
			||||
 | 
					            mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr); | 
				
			||||
 | 
					            break; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void DisplayMono::printText(const char* text, uint8_t line, uint8_t dispX) { | 
				
			||||
 | 
					    if (!mIsLarge) { | 
				
			||||
 | 
					        dispX = (line == 0) ? 10 : 5; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					    setFont(line); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    dispX += (mEnScreenSaver) ? (_mExtra % 7) : 0; | 
				
			||||
 | 
					    mDisplay->drawStr(dispX, mLineOffsets[line], text); | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,38 @@ | 
				
			|||||
 | 
					// SPDX-License-Identifier: GPL-2.0-or-later
 | 
				
			||||
 | 
					#pragma once | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include <U8g2lib.h> | 
				
			||||
 | 
					#define DISP_DEFAULT_TIMEOUT 60  // in seconds
 | 
				
			||||
 | 
					#define DISP_FMT_TEXT_LEN    32 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					class DisplayMono { | 
				
			||||
 | 
					   public: | 
				
			||||
 | 
					      DisplayMono(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char* version); | 
				
			||||
 | 
					      void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum); | 
				
			||||
 | 
					      void loop(void); | 
				
			||||
 | 
					      void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					   private: | 
				
			||||
 | 
					      void calcLineHeights(); | 
				
			||||
 | 
					      void setFont(uint8_t line); | 
				
			||||
 | 
					      void printText(const char* text, uint8_t line, uint8_t dispX = 5); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      U8G2* mDisplay; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      uint8_t mType; | 
				
			||||
 | 
					      bool mEnPowerSafe, mEnScreenSaver; | 
				
			||||
 | 
					      uint8_t mLuminance; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      bool mIsLarge = false; | 
				
			||||
 | 
					      uint8_t mLoopCnt; | 
				
			||||
 | 
					      uint32_t* mUtcTs; | 
				
			||||
 | 
					      uint8_t mLineOffsets[5]; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      uint16_t _dispY; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      uint8_t _mExtra; | 
				
			||||
 | 
					      uint16_t mTimeout; | 
				
			||||
 | 
					      char _fmtText[DISP_FMT_TEXT_LEN]; | 
				
			||||
 | 
					}; | 
				
			||||
@ -0,0 +1,197 @@ | 
				
			|||||
 | 
					#include "Display_ePaper.h" | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#ifdef ESP8266 | 
				
			||||
 | 
					    #include <ESP8266WiFi.h> | 
				
			||||
 | 
					#elif defined(ESP32) | 
				
			||||
 | 
					    #include <WiFi.h> | 
				
			||||
 | 
					#endif | 
				
			||||
 | 
					#include "../../utils/helper.h" | 
				
			||||
 | 
					#include "imagedata.h" | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#if defined(ESP32) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					static const uint32_t spiClk = 4000000;  // 4 MHz
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#if defined(ESP32) && defined(USE_HSPI_FOR_EPD) | 
				
			||||
 | 
					SPIClass hspi(HSPI); | 
				
			||||
 | 
					#endif | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					DisplayEPaper::DisplayEPaper() { | 
				
			||||
 | 
					    mDisplayRotation = 2; | 
				
			||||
 | 
					    mHeadFootPadding = 16; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//***************************************************************************
 | 
				
			||||
 | 
					void DisplayEPaper::init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, uint32_t *utcTs, const char *version) { | 
				
			||||
 | 
					    mUtcTs = utcTs; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    if (type > 9) { | 
				
			||||
 | 
					        Serial.begin(115200); | 
				
			||||
 | 
					        _display = new GxEPD2_BW<GxEPD2_150_BN, GxEPD2_150_BN::HEIGHT>(GxEPD2_150_BN(_CS, _DC, _RST, _BUSY)); | 
				
			||||
 | 
					        hspi.begin(_SCK, _BUSY, _MOSI, _CS); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#if defined(ESP32) && defined(USE_HSPI_FOR_EPD) | 
				
			||||
 | 
					        _display->epd2.selectSPI(hspi, SPISettings(spiClk, MSBFIRST, SPI_MODE0)); | 
				
			||||
 | 
					#endif | 
				
			||||
 | 
					        _display->init(115200, true, 2, false); | 
				
			||||
 | 
					        _display->setRotation(mDisplayRotation); | 
				
			||||
 | 
					        _display->setFullWindow(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        // Logo
 | 
				
			||||
 | 
					        _display->fillScreen(GxEPD_BLACK); | 
				
			||||
 | 
					        _display->drawBitmap(0, 0, logo, 200, 200, GxEPD_WHITE); | 
				
			||||
 | 
					        while (_display->nextPage()) | 
				
			||||
 | 
					            ; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        // clean the screen
 | 
				
			||||
 | 
					        delay(2000); | 
				
			||||
 | 
					        _display->fillScreen(GxEPD_WHITE); | 
				
			||||
 | 
					        while (_display->nextPage()) | 
				
			||||
 | 
					            ; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        headlineIP(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        // call the PowerPage to change the PV Power Values
 | 
				
			||||
 | 
					        actualPowerPaged(0, 0, 0, 0); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void DisplayEPaper::config(uint8_t rotation) { | 
				
			||||
 | 
					    mDisplayRotation = rotation; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//***************************************************************************
 | 
				
			||||
 | 
					void DisplayEPaper::fullRefresh() { | 
				
			||||
 | 
					    // screen complete black
 | 
				
			||||
 | 
					    _display->fillScreen(GxEPD_BLACK); | 
				
			||||
 | 
					    while (_display->nextPage()) | 
				
			||||
 | 
					        ; | 
				
			||||
 | 
					    delay(2000); | 
				
			||||
 | 
					    // screen complete white
 | 
				
			||||
 | 
					    _display->fillScreen(GxEPD_WHITE); | 
				
			||||
 | 
					    while (_display->nextPage()) | 
				
			||||
 | 
					        ; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					//***************************************************************************
 | 
				
			||||
 | 
					void DisplayEPaper::headlineIP() { | 
				
			||||
 | 
					    int16_t tbx, tby; | 
				
			||||
 | 
					    uint16_t tbw, tbh; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    _display->setFont(&FreeSans9pt7b); | 
				
			||||
 | 
					    _display->setTextColor(GxEPD_WHITE); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    _display->setPartialWindow(0, 0, _display->width(), mHeadFootPadding); | 
				
			||||
 | 
					    _display->fillScreen(GxEPD_BLACK); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    do { | 
				
			||||
 | 
					        if ((WiFi.isConnected() == true) && (WiFi.localIP() > 0)) { | 
				
			||||
 | 
					            snprintf(_fmtText, sizeof(_fmtText), "%s", WiFi.localIP().toString().c_str()); | 
				
			||||
 | 
					        } else { | 
				
			||||
 | 
					            snprintf(_fmtText, sizeof(_fmtText), "WiFi not connected"); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					        _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); | 
				
			||||
 | 
					        uint16_t x = ((_display->width() - tbw) / 2) - tbx; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        _display->setCursor(x, (mHeadFootPadding - 2)); | 
				
			||||
 | 
					        _display->println(_fmtText); | 
				
			||||
 | 
					    } while (_display->nextPage()); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					//***************************************************************************
 | 
				
			||||
 | 
					void DisplayEPaper::lastUpdatePaged() { | 
				
			||||
 | 
					    int16_t tbx, tby; | 
				
			||||
 | 
					    uint16_t tbw, tbh; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    _display->setFont(&FreeSans9pt7b); | 
				
			||||
 | 
					    _display->setTextColor(GxEPD_WHITE); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    _display->setPartialWindow(0, _display->height() - mHeadFootPadding, _display->width(), mHeadFootPadding); | 
				
			||||
 | 
					    _display->fillScreen(GxEPD_BLACK); | 
				
			||||
 | 
					    do { | 
				
			||||
 | 
					        if (NULL != mUtcTs) { | 
				
			||||
 | 
					            snprintf(_fmtText, sizeof(_fmtText), ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str()); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); | 
				
			||||
 | 
					            uint16_t x = ((_display->width() - tbw) / 2) - tbx; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            _display->setCursor(x, (_display->height() - 3)); | 
				
			||||
 | 
					            _display->println(_fmtText); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					    } while (_display->nextPage()); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					//***************************************************************************
 | 
				
			||||
 | 
					void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod) { | 
				
			||||
 | 
					    int16_t tbx, tby; | 
				
			||||
 | 
					    uint16_t tbw, tbh, x, y; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    _display->setFont(&FreeSans24pt7b); | 
				
			||||
 | 
					    _display->setTextColor(GxEPD_BLACK); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    _display->setPartialWindow(0, mHeadFootPadding, _display->width(), _display->height() - (mHeadFootPadding * 2)); | 
				
			||||
 | 
					    _display->fillScreen(GxEPD_WHITE); | 
				
			||||
 | 
					    do { | 
				
			||||
 | 
					        if (_totalPower > 9999) { | 
				
			||||
 | 
					            snprintf(_fmtText, sizeof(_fmtText), "%.1f kW", (_totalPower / 10000)); | 
				
			||||
 | 
					            _changed = true; | 
				
			||||
 | 
					        } else if ((_totalPower > 0) && (_totalPower <= 9999)) { | 
				
			||||
 | 
					            snprintf(_fmtText, sizeof(_fmtText), "%.0f W", _totalPower); | 
				
			||||
 | 
					            _changed = true; | 
				
			||||
 | 
					        } else { | 
				
			||||
 | 
					            snprintf(_fmtText, sizeof(_fmtText), "offline"); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					        _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); | 
				
			||||
 | 
					        x = ((_display->width() - tbw) / 2) - tbx; | 
				
			||||
 | 
					        _display->setCursor(x, mHeadFootPadding + tbh + 10); | 
				
			||||
 | 
					        _display->print(_fmtText); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        _display->setFont(&FreeSans12pt7b); | 
				
			||||
 | 
					        y = _display->height() / 2; | 
				
			||||
 | 
					        _display->setCursor(5, y); | 
				
			||||
 | 
					        _display->print("today:"); | 
				
			||||
 | 
					        snprintf(_fmtText, _display->width(), "%.0f", _totalYieldDay); | 
				
			||||
 | 
					        _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); | 
				
			||||
 | 
					        x = ((_display->width() - tbw) / 2) - tbx; | 
				
			||||
 | 
					        _display->setCursor(x, y); | 
				
			||||
 | 
					        _display->print(_fmtText); | 
				
			||||
 | 
					        _display->setCursor(_display->width() - 38, y); | 
				
			||||
 | 
					        _display->println("Wh"); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        y = y + tbh + 7; | 
				
			||||
 | 
					        _display->setCursor(5, y); | 
				
			||||
 | 
					        _display->print("total:"); | 
				
			||||
 | 
					        snprintf(_fmtText, _display->width(), "%.1f", _totalYieldTotal); | 
				
			||||
 | 
					        _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); | 
				
			||||
 | 
					        x = ((_display->width() - tbw) / 2) - tbx; | 
				
			||||
 | 
					        _display->setCursor(x, y); | 
				
			||||
 | 
					        _display->print(_fmtText); | 
				
			||||
 | 
					        _display->setCursor(_display->width() - 50, y); | 
				
			||||
 | 
					        _display->println("kWh"); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        _display->setCursor(10, _display->height() - (mHeadFootPadding + 10)); | 
				
			||||
 | 
					        snprintf(_fmtText, sizeof(_fmtText), "%d Inverter online", _isprod); | 
				
			||||
 | 
					        _display->println(_fmtText); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    } while (_display->nextPage()); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					//***************************************************************************
 | 
				
			||||
 | 
					void DisplayEPaper::loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { | 
				
			||||
 | 
					    // check if the IP has changed
 | 
				
			||||
 | 
					    if (_settedIP != WiFi.localIP().toString().c_str()) { | 
				
			||||
 | 
					        // save the new IP and call the Headline Funktion to adapt the Headline
 | 
				
			||||
 | 
					        _settedIP = WiFi.localIP().toString().c_str(); | 
				
			||||
 | 
					        headlineIP(); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    // call the PowerPage to change the PV Power Values
 | 
				
			||||
 | 
					    actualPowerPaged(totalPower, totalYieldDay, totalYieldTotal, isprod); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    // if there was an change and the Inverter is producing set a new Timestam in the footline
 | 
				
			||||
 | 
					    if ((isprod > 0) && (_changed)) { | 
				
			||||
 | 
					        _changed = false; | 
				
			||||
 | 
					        lastUpdatePaged(); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    _display->powerOff(); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					//***************************************************************************
 | 
				
			||||
 | 
					#endif // ESP32
 | 
				
			||||
@ -0,0 +1,52 @@ | 
				
			|||||
 | 
					// SPDX-License-Identifier: GPL-2.0-or-later
 | 
				
			||||
 | 
					#pragma once | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#if defined(ESP32) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					// uncomment next line to use HSPI for EPD (and VSPI for SD), e.g. with Waveshare ESP32 Driver Board
 | 
				
			||||
 | 
					#define USE_HSPI_FOR_EPD | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/// uncomment next line to use class GFX of library GFX_Root instead of Adafruit_GFX, to use less code and ram
 | 
				
			||||
 | 
					// #include <GFX.h>
 | 
				
			||||
 | 
					// base class GxEPD2_GFX can be used to pass references or pointers to the display instance as parameter, uses ~1.2k more code
 | 
				
			||||
 | 
					// enable GxEPD2_GFX base class
 | 
				
			||||
 | 
					#define ENABLE_GxEPD2_GFX 1 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include <GxEPD2_3C.h> | 
				
			||||
 | 
					#include <GxEPD2_BW.h> | 
				
			||||
 | 
					#include <SPI.h> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include <map> | 
				
			||||
 | 
					// FreeFonts from Adafruit_GFX
 | 
				
			||||
 | 
					#include <Fonts/FreeSans12pt7b.h> | 
				
			||||
 | 
					#include <Fonts/FreeSans18pt7b.h> | 
				
			||||
 | 
					#include <Fonts/FreeSans24pt7b.h> | 
				
			||||
 | 
					#include <Fonts/FreeSans9pt7b.h> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					// GDEW027C44   2.7 " b/w/r 176x264, IL91874
 | 
				
			||||
 | 
					// GDEH0154D67  1.54" b/w   200x200
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					class DisplayEPaper { | 
				
			||||
 | 
					   public: | 
				
			||||
 | 
					      DisplayEPaper(); | 
				
			||||
 | 
					      void fullRefresh(); | 
				
			||||
 | 
					      void init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, uint32_t *utcTs, const char* version); | 
				
			||||
 | 
					      void config(uint8_t rotation); | 
				
			||||
 | 
					      void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					   private: | 
				
			||||
 | 
					      void headlineIP(); | 
				
			||||
 | 
					      void actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod); | 
				
			||||
 | 
					      void lastUpdatePaged(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      uint8_t mDisplayRotation; | 
				
			||||
 | 
					      bool _changed = false; | 
				
			||||
 | 
					      char _fmtText[35]; | 
				
			||||
 | 
					      const char* _settedIP; | 
				
			||||
 | 
					      uint8_t mHeadFootPadding; | 
				
			||||
 | 
					      GxEPD2_GFX* _display; | 
				
			||||
 | 
					      uint32_t *mUtcTs; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#endif // ESP32
 | 
				
			||||
@ -0,0 +1,329 @@ | 
				
			|||||
 | 
					// GxEPD2_ESP32_ESP8266_WifiData_V1_und_V2
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#ifndef __IMAGEDATA_H__ | 
				
			||||
 | 
					#define __IMAGEDATA_H__ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#if defined(__AVR__) || defined(ARDUINO_ARCH_SAMD) | 
				
			||||
 | 
					#include <avr/pgmspace.h> | 
				
			||||
 | 
					#elif defined(ESP8266) || defined(ESP32) | 
				
			||||
 | 
					#include <pgmspace.h> | 
				
			||||
 | 
					#endif | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					// 'Logo', 200x200px
 | 
				
			||||
 | 
					const unsigned char logo[] PROGMEM = { | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x5f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, | 
				
			||||
 | 
					    0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x06, | 
				
			||||
 | 
					    0x0f, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x7e, 0x0f, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, | 
				
			||||
 | 
					    0x03, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x19, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xfe, | 
				
			||||
 | 
					    0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xe0, 0x70, 0x7f, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe0, 0x3f, 0x07, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xfc, 0x0f, 0xe0, 0x3f, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xe0, 0x1f, 0x83, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xf1, 0xff, 0xe0, 0x1f, 0x83, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xe0, | 
				
			||||
 | 
					    0x0f, 0x83, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xe0, 0x0f, 0x83, 0xff, 0xff, 0xff, 0xff, 0xfe, | 
				
			||||
 | 
					    0x07, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, | 
				
			||||
 | 
					    0xff, 0xc1, 0x07, 0x80, 0x07, 0xfe, 0xff, 0xff, 0xfc, 0x07, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xe1, 0x07, 0xc0, 0x01, 0xe0, 0x0f, | 
				
			||||
 | 
					    0xff, 0xfc, 0x0f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xfc, 0xff, 0xe1, 0x83, 0xc0, 0x01, 0xc0, 0x07, 0xff, 0xf8, 0x0f, 0xfc, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xe1, 0x83, 0xc0, 0x00, | 
				
			||||
 | 
					    0xc0, 0x07, 0x8f, 0xf8, 0x1f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xfe, 0x7f, 0xe0, 0x01, 0xc0, 0x00, 0x81, 0x83, 0x07, 0xf0, 0x3f, 0xf9, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xe0, 0x01, | 
				
			||||
 | 
					    0xe0, 0xe0, 0x87, 0xe3, 0x0f, 0xf0, 0x3f, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xe0, 0x00, 0xe0, 0xe0, 0x87, 0xe1, 0x0c, 0x60, 0x7f, | 
				
			||||
 | 
					    0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, | 
				
			||||
 | 
					    0xe0, 0x00, 0xe1, 0xf0, 0x87, 0xe1, 0x08, 0x60, 0x7f, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xe0, 0xe0, 0xe0, 0xe0, 0x87, 0xc2, 0x00, | 
				
			||||
 | 
					    0x40, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0x8f, 0xc0, 0xe0, 0x60, 0xe0, 0xc0, 0x82, 0x00, 0xc0, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xc0, 0xe0, 0x60, 0xe0, 0xc0, | 
				
			||||
 | 
					    0x06, 0x01, 0x81, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xcf, 0xe0, 0xe0, 0x20, 0xe0, 0xe0, 0x0c, 0x03, 0x81, 0xff, 0x1f, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xc0, 0xf0, 0x30, | 
				
			||||
 | 
					    0xe1, 0xf8, 0x18, 0x07, 0xe1, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xc0, 0xf0, 0x7f, 0xff, 0xff, 0xf0, 0x1f, 0xf3, 0xfe, 0x01, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xc0, | 
				
			||||
 | 
					    0xfb, 0xff, 0xff, 0xff, 0xe0, 0x3e, 0x1f, 0xfc, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xfc, 0x0f, | 
				
			||||
 | 
					    0xf8, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, | 
				
			||||
 | 
					    0x33, 0xef, 0xff, 0xff, 0xff, 0xff, 0x81, 0xfc, 0x0f, 0xf1, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xf1, 0xff, 0xff, 0xa0, 0x00, 0x7f, 0xe3, | 
				
			||||
 | 
					    0xfc, 0x0f, 0xf3, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xf1, 0xf9, 0xff, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xfc, 0x0f, 0xe7, 0xff, 0xe0, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xf9, 0xff, 0x80, 0x3f, 0xff, | 
				
			||||
 | 
					    0xe0, 0x0f, 0xfe, 0x1f, 0xc7, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xcf, 0xf8, 0xf0, 0x07, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0x8f, 0xff, 0xfc, | 
				
			||||
 | 
					    0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x70, 0x3f, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0x1f, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xfc, 0x63, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0x3f, | 
				
			||||
 | 
					    0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfe, | 
				
			||||
 | 
					    0x23, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7e, 0x3f, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x23, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, | 
				
			||||
 | 
					    0x0c, 0x7f, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, | 
				
			||||
 | 
					    0x7f, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xfc, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xf8, | 
				
			||||
 | 
					    0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x87, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x00, 0x3f, 0xff, 0xf8, 0x00, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xfc, 0x00, 0x00, 0x01, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x55, 0x00, 0x3f, 0xf8, 0x00, | 
				
			||||
 | 
					    0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0x01, 0xff, 0xff, 0xf8, 0x0f, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0x9f, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0x03, | 
				
			||||
 | 
					    0xfc, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xe3, 0xf1, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xe0, 0xfe, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xe7, 0xf9, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xf8, 0x7e, 0x06, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xcf, | 
				
			||||
 | 
					    0xf8, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0x03, 0x3f, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0x1f, 0x23, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, | 
				
			||||
 | 
					    0xff, 0x9f, 0xfe, 0x7f, 0xff, 0xf3, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf1, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0x9f, 0xfe, 0x7f, 0xff, 0xe3, 0xf8, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xfe, 0x7f, 0xff, 0x9f, 0xff, 0x0f, 0xff, 0x8f, 0xf1, 0xff, 0xff, 0xff, 0xfe, 0xf5, 0x90, 0x07, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0x9f, 0xff, 0x03, 0xff, | 
				
			||||
 | 
					    0x1f, 0xe3, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xfe, 0x7f, 0xff, 0x3f, 0xfe, 0x31, 0xfe, 0x7f, 0xe7, 0xff, 0x80, 0x00, 0x40, 0x00, | 
				
			||||
 | 
					    0x07, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0x3f, 0x3c, | 
				
			||||
 | 
					    0xf9, 0xfc, 0xff, 0xe7, 0xfe, 0x3f, 0xc9, 0xff, 0xf1, 0x1f, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x3f, 0x3c, 0xf9, 0xf9, 0xff, 0xc7, 0xfc, 0xff, 0x90, | 
				
			||||
 | 
					    0x7f, 0xf3, 0x03, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, | 
				
			||||
 | 
					    0x3f, 0x39, 0xfd, 0xf3, 0xff, 0xcf, 0xfc, 0xff, 0x90, 0x3f, 0xf3, 0x83, 0xf8, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x3f, 0x39, 0xf9, 0xc7, 0xff, 0xcf, 0xfc, | 
				
			||||
 | 
					    0xff, 0x32, 0x7f, 0xe4, 0x77, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, | 
				
			||||
 | 
					    0xff, 0xff, 0x7f, 0x33, 0xf9, 0x8f, 0xff, 0xcf, 0xf9, 0xff, 0x00, 0x7f, 0xe0, 0x67, 0xfc, 0x7f, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x7f, 0xb3, 0xf3, 0xbf, 0xff, | 
				
			||||
 | 
					    0xcf, 0xf9, 0xff, 0x00, 0xff, 0xfe, 0x47, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xf9, 0xff, 0xff, 0x7f, 0xf7, 0xf3, 0xff, 0xff, 0xcf, 0xf9, 0xff, 0xe0, 0xff, 0xfc, 0x0f, | 
				
			||||
 | 
					    0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x7f, 0xe7, 0xe7, | 
				
			||||
 | 
					    0xff, 0xff, 0xcf, 0xf9, 0xff, 0xe1, 0xff, 0xfc, 0x1f, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x3f, 0xef, 0xe7, 0xef, 0xff, 0xc7, 0xf9, 0xff, 0xc3, 0xff, | 
				
			||||
 | 
					    0xfc, 0x3f, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x3f, | 
				
			||||
 | 
					    0xef, 0xef, 0xc0, 0xff, 0xe7, 0xf9, 0xff, 0xc3, 0xff, 0xf8, 0x3f, 0xff, 0x3f, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x3f, 0xef, 0xcf, 0xf0, 0x01, 0xe7, 0xf1, 0xff, | 
				
			||||
 | 
					    0x87, 0xff, 0xf8, 0x7f, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, | 
				
			||||
 | 
					    0xff, 0xbf, 0xcf, 0xe7, 0xff, 0xc1, 0xe3, 0xe1, 0xff, 0x8f, 0xff, 0xf0, 0xff, 0xff, 0x9f, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x9f, 0xef, 0xe7, 0xff, 0xff, 0xf3, | 
				
			||||
 | 
					    0xc1, 0xff, 0x96, 0xaf, 0xf9, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xf9, 0xff, 0xff, 0x9f, 0xe7, 0xe3, 0xff, 0xff, 0xf1, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, | 
				
			||||
 | 
					    0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xcf, 0xe7, 0xf3, 0xff, | 
				
			||||
 | 
					    0xff, 0xf8, 0xc0, 0x00, 0x4a, 0x90, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xf9, 0xff, 0xff, 0xef, 0xf3, 0xf3, 0x9f, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, | 
				
			||||
 | 
					    0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xe7, 0xf1, | 
				
			||||
 | 
					    0xe7, 0xc7, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xf3, 0xf0, 0x07, 0xe3, 0xff, 0xff, 0x81, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, | 
				
			||||
 | 
					    0xf8, 0x07, 0x1f, 0xf1, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xfc, 0x1f, 0x9f, 0xf8, 0xff, 0xff, 0xc3, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, | 
				
			||||
 | 
					    0xff, 0xff, 0xf8, 0xff, 0x9f, 0xfe, 0x7f, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xf9, 0xff, 0x9f, 0xfe, 0x3f, | 
				
			||||
 | 
					    0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xfd, 0xff, 0xff, 0xf1, 0xff, 0x9f, 0xff, 0x9f, 0xff, 0xf3, 0xff, 0x3f, 0x3f, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xe1, 0xff, 0xcf, | 
				
			||||
 | 
					    0xff, 0xc7, 0xff, 0xf3, 0xff, 0x3f, 0x9f, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xe1, 0xff, 0x8f, 0xff, 0xe7, 0xff, 0xf3, 0xff, 0x3f, 0x9f, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xc1, | 
				
			||||
 | 
					    0xff, 0xcf, 0xff, 0xf3, 0xff, 0xf3, 0xff, 0x3f, 0x9f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x81, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xf3, 0xff, | 
				
			||||
 | 
					    0x3f, 0x9f, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, | 
				
			||||
 | 
					    0xff, 0x91, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0x3f, 0x9f, 0xff, 0xff, 0xfe, 0x3f, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0x11, 0xff, 0x9f, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xf3, 0xff, 0x1f, 0x9f, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xfe, 0x7f, 0xff, 0x21, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xbf, 0x9f, 0xff, 0xff, 0xfe, | 
				
			||||
 | 
					    0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x20, 0xff, 0x9f, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x60, 0x7f, 0x9f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0x64, 0x3f, | 
				
			||||
 | 
					    0x1f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0xe7, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0x3f, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xfc, | 
				
			||||
 | 
					    0xe7, 0x80, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xf1, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xf8, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, | 
				
			||||
 | 
					    0xff, 0xff, 0xfe, 0x7f, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0x9f, 0xf9, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xe7, 0xff, 0xfe, 0x7f, 0xff, 0xc3, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xf9, 0xe7, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xf3, 0xf3, 0xff, 0xfc, 0x7f, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xcf, 0xf9, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xf3, 0xff, 0xf8, 0xff, 0xff, | 
				
			||||
 | 
					    0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xf9, 0xe7, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xf3, 0xf9, 0xff, 0xe1, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xe7, 0xf3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0x3f, 0x07, | 
				
			||||
 | 
					    0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xf3, 0xe7, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfe, 0x00, 0x1f, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xf3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, | 
				
			||||
 | 
					    0xe0, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, | 
				
			||||
 | 
					    0xf3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xe3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xf7, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xf8, 0x83, 0xe7, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x13, 0xe7, 0xff, 0xfc, 0x03, | 
				
			||||
 | 
					    0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xfc, 0x31, 0xe7, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xfe, | 
				
			||||
 | 
					    0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x39, 0xe3, 0xff, | 
				
			||||
 | 
					    0xfc, 0x00, 0x1f, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x31, 0xf3, 0xff, 0xfc, 0x00, 0x1f, 0xff, 0xc7, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, | 
				
			||||
 | 
					    0xf3, 0xff, 0xfc, 0x00, 0x07, 0xff, 0x87, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xf3, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x07, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0x83, 0xf3, 0xff, 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xf3, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xf8, 0x07, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xe3, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xfe, 0x1f, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xe1, 0xff, 0xfe, | 
				
			||||
 | 
					    0x01, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xe1, 0xff, 0xf0, 0x00, 0x3f, 0x80, 0x07, 0xff, 0xff, 0xf0, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x4c, | 
				
			||||
 | 
					    0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x0c, 0xff, 0xf0, 0x00, 0x00, 0x0b, 0x87, 0xff, | 
				
			||||
 | 
					    0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0x0e, 0x7f, 0xf8, 0x00, 0x3f, 0xff, 0xc7, 0xff, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x86, 0x7f, 0xfe, 0x00, 0xff, 0xff, | 
				
			||||
 | 
					    0xc3, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0x80, 0x7f, 0xff, 0x87, 0xff, 0xff, 0xf3, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xf3, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfe, 0x07, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xf3, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xf3, 0xc0, 0x7f, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xe3, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xe0, | 
				
			||||
 | 
					    0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x03, 0xff, | 
				
			||||
 | 
					    0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xf0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa0, 0x17, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | 
				
			||||
 | 
					    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#endif /*__IMAGEDATA_H__*/ | 
				
			||||
@ -1,307 +0,0 @@ | 
				
			|||||
#ifndef __MONOCHROME_DISPLAY__ | 
					 | 
				
			||||
#define __MONOCHROME_DISPLAY__ | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#if defined(ENA_NOKIA) || defined(ENA_SSD1306) | 
					 | 
				
			||||
#ifdef ENA_NOKIA | 
					 | 
				
			||||
    #include <U8g2lib.h> | 
					 | 
				
			||||
    #define DISP_PROGMEM U8X8_PROGMEM | 
					 | 
				
			||||
#else // ENA_SSD1306
 | 
					 | 
				
			||||
    /* esp8266 : SCL = 5, SDA = 4 */ | 
					 | 
				
			||||
    /* ewsp32  : SCL = 22, SDA = 21 */ | 
					 | 
				
			||||
    #include <Wire.h> | 
					 | 
				
			||||
    #include <SSD1306Wire.h> | 
					 | 
				
			||||
    #define DISP_PROGMEM PROGMEM | 
					 | 
				
			||||
#endif | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#include <Timezone.h> | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#include "../../utils/helper.h" | 
					 | 
				
			||||
#include "../../hm/hmSystem.h" | 
					 | 
				
			||||
 | 
					 | 
				
			||||
static uint8_t bmp_arrow[] DISP_PROGMEM = { | 
					 | 
				
			||||
    B00000000, B00011100, B00011100, B00001110, B00001110, B11111110, B01111111, | 
					 | 
				
			||||
    B01110000, B01110000, B00110000, B00111000, B00011000, B01111111, B00111111, | 
					 | 
				
			||||
    B00011110, B00001110, B00000110, B00000000, B00000000, B00000000, B00000000}; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
static TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};     // Central European Summer Time
 | 
					 | 
				
			||||
static TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};       // Central European Standard Tim
 | 
					 | 
				
			||||
 | 
					 | 
				
			||||
template<class HMSYSTEM> | 
					 | 
				
			||||
class MonochromeDisplay { | 
					 | 
				
			||||
    public: | 
					 | 
				
			||||
        #if defined(ENA_NOKIA) | 
					 | 
				
			||||
        MonochromeDisplay() : mDisplay(U8G2_R0, 5, 4, 16), mCE(CEST, CET) { | 
					 | 
				
			||||
            mNewPayload = false; | 
					 | 
				
			||||
            mExtra      = 0; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
        #else // ENA_SSD1306
 | 
					 | 
				
			||||
        MonochromeDisplay() : mDisplay(0x3c, SDA, SCL), mCE(CEST, CET) { | 
					 | 
				
			||||
            mNewPayload = false; | 
					 | 
				
			||||
            mExtra      = 0; | 
					 | 
				
			||||
            mRx         = 0; | 
					 | 
				
			||||
            mUp         = 1; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
        #endif | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void setup(HMSYSTEM *sys, uint32_t *utcTs) { | 
					 | 
				
			||||
            mSys   = sys; | 
					 | 
				
			||||
            mUtcTs = utcTs; | 
					 | 
				
			||||
            memset( mToday, 0, sizeof(float)*MAX_NUM_INVERTERS ); | 
					 | 
				
			||||
            memset( mTotal, 0, sizeof(float)*MAX_NUM_INVERTERS ); | 
					 | 
				
			||||
            mLastHour = 25; | 
					 | 
				
			||||
            #if defined(ENA_NOKIA) | 
					 | 
				
			||||
                mDisplay.begin(); | 
					 | 
				
			||||
                ShowInfoText("booting..."); | 
					 | 
				
			||||
            #else | 
					 | 
				
			||||
                mDisplay.init(); | 
					 | 
				
			||||
                mDisplay.flipScreenVertically(); | 
					 | 
				
			||||
                mDisplay.setContrast(63); | 
					 | 
				
			||||
                mDisplay.setBrightness(63); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                mDisplay.clear(); | 
					 | 
				
			||||
                mDisplay.setFont(ArialMT_Plain_24); | 
					 | 
				
			||||
                mDisplay.setTextAlignment(TEXT_ALIGN_CENTER_BOTH); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                mDisplay.drawString(64,22,"Starting..."); | 
					 | 
				
			||||
                mDisplay.display(); | 
					 | 
				
			||||
                mDisplay.setTextAlignment(TEXT_ALIGN_LEFT); | 
					 | 
				
			||||
            #endif | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void loop(void) { | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void payloadEventListener(uint8_t cmd) { | 
					 | 
				
			||||
            mNewPayload = true; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void tickerSecond() { | 
					 | 
				
			||||
            static int cnt=1; | 
					 | 
				
			||||
            if(mNewPayload || !(cnt % 10)) { | 
					 | 
				
			||||
                cnt=1; | 
					 | 
				
			||||
                mNewPayload = false; | 
					 | 
				
			||||
                DataScreen(); | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            else | 
					 | 
				
			||||
               cnt++; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
    private: | 
					 | 
				
			||||
        #if defined(ENA_NOKIA) | 
					 | 
				
			||||
        void ShowInfoText(const char *txt) { | 
					 | 
				
			||||
            /* u8g2_font_open_iconic_embedded_2x_t 'D' + 'G' + 'J' */ | 
					 | 
				
			||||
            mDisplay.clear(); | 
					 | 
				
			||||
            mDisplay.firstPage(); | 
					 | 
				
			||||
            do { | 
					 | 
				
			||||
                const char *e; | 
					 | 
				
			||||
                const char *p = txt; | 
					 | 
				
			||||
                int y=10; | 
					 | 
				
			||||
                mDisplay.setFont(u8g2_font_5x8_tr); | 
					 | 
				
			||||
                while(1) { | 
					 | 
				
			||||
                    for(e=p+1; (*e && (*e != '\n')); e++); | 
					 | 
				
			||||
                    size_t len=e-p; | 
					 | 
				
			||||
                    mDisplay.setCursor(2,y); | 
					 | 
				
			||||
                    String res=((String)p).substring(0,len); | 
					 | 
				
			||||
                    mDisplay.print(res); | 
					 | 
				
			||||
                    if ( !*e ) | 
					 | 
				
			||||
                        break; | 
					 | 
				
			||||
                    p=e+1; | 
					 | 
				
			||||
                    y+=12; | 
					 | 
				
			||||
                } | 
					 | 
				
			||||
                mDisplay.sendBuffer(); | 
					 | 
				
			||||
            } while( mDisplay.nextPage() ); | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
        #endif | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void DataScreen(void) { | 
					 | 
				
			||||
            String timeStr = ah::getDateTimeStr(mCE.toLocal(*mUtcTs)).substring(2, 22); | 
					 | 
				
			||||
            int hr = timeStr.substring(9,2).toInt(); | 
					 | 
				
			||||
            IPAddress ip = WiFi.localIP(); | 
					 | 
				
			||||
            float totalYield = 0.0, totalYieldToday = 0.0, totalActual = 0.0; | 
					 | 
				
			||||
            char fmtText[32]; | 
					 | 
				
			||||
            int  ucnt=0, num_inv=0; | 
					 | 
				
			||||
            unsigned int pow_i[ MAX_NUM_INVERTERS ]; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
            memset( pow_i, 0, sizeof(unsigned int)* MAX_NUM_INVERTERS ); | 
					 | 
				
			||||
            if ( hr < mLastHour )  // next day ? reset today-values
 | 
					 | 
				
			||||
                memset( mToday, 0, sizeof(float)*MAX_NUM_INVERTERS ); | 
					 | 
				
			||||
            mLastHour = hr; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
            for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { | 
					 | 
				
			||||
                Inverter<> *iv = mSys->getInverterByPos(id); | 
					 | 
				
			||||
                if (NULL != iv) { | 
					 | 
				
			||||
                    record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); | 
					 | 
				
			||||
                    uint8_t pos; | 
					 | 
				
			||||
                    uint8_t list[] = {FLD_PAC, FLD_YT, FLD_YD}; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                    for (uint8_t fld = 0; fld < 3; fld++) { | 
					 | 
				
			||||
                        pos = iv->getPosByChFld(CH0, list[fld],rec); | 
					 | 
				
			||||
                        int isprod = iv->isProducing(*mUtcTs,rec); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                        if(fld == 1) | 
					 | 
				
			||||
                        { | 
					 | 
				
			||||
                            if ( isprod ) | 
					 | 
				
			||||
                                mTotal[num_inv] = iv->getValue(pos,rec); | 
					 | 
				
			||||
                            totalYield += mTotal[num_inv]; | 
					 | 
				
			||||
                        } | 
					 | 
				
			||||
                        if(fld == 2) | 
					 | 
				
			||||
                        { | 
					 | 
				
			||||
                            if ( isprod ) | 
					 | 
				
			||||
                                mToday[num_inv] = iv->getValue(pos,rec); | 
					 | 
				
			||||
                            totalYieldToday += mToday[num_inv]; | 
					 | 
				
			||||
                        } | 
					 | 
				
			||||
                        if((fld == 0) && isprod ) | 
					 | 
				
			||||
                        { | 
					 | 
				
			||||
                            pow_i[num_inv] = iv->getValue(pos,rec); | 
					 | 
				
			||||
                            totalActual += iv->getValue(pos,rec); | 
					 | 
				
			||||
                            ucnt++; | 
					 | 
				
			||||
                        } | 
					 | 
				
			||||
                    } | 
					 | 
				
			||||
                    num_inv++; | 
					 | 
				
			||||
                } | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            /* u8g2_font_open_iconic_embedded_2x_t 'D' + 'G' + 'J' */ | 
					 | 
				
			||||
            mDisplay.clear(); | 
					 | 
				
			||||
            #if defined(ENA_NOKIA) | 
					 | 
				
			||||
                mDisplay.firstPage(); | 
					 | 
				
			||||
                do { | 
					 | 
				
			||||
                    if(ucnt) { | 
					 | 
				
			||||
                        mDisplay.drawXBMP(10,1,8,17,bmp_arrow); | 
					 | 
				
			||||
                        mDisplay.setFont(u8g2_font_logisoso16_tr); | 
					 | 
				
			||||
                        mDisplay.setCursor(25,17); | 
					 | 
				
			||||
                        sprintf(fmtText,"%3.0f",totalActual); | 
					 | 
				
			||||
                        mDisplay.print(String(fmtText)+F(" W")); | 
					 | 
				
			||||
                    } | 
					 | 
				
			||||
                    else | 
					 | 
				
			||||
                    { | 
					 | 
				
			||||
                        mDisplay.setFont(u8g2_font_logisoso16_tr  ); | 
					 | 
				
			||||
                        mDisplay.setCursor(10,17); | 
					 | 
				
			||||
                        mDisplay.print(String(F("offline"))); | 
					 | 
				
			||||
                    } | 
					 | 
				
			||||
                    mDisplay.drawHLine(2,20,78); | 
					 | 
				
			||||
                    mDisplay.setFont(u8g2_font_5x8_tr); | 
					 | 
				
			||||
                    mDisplay.setCursor(5,29); | 
					 | 
				
			||||
                    if (( num_inv < 2 ) || !(mExtra%2)) | 
					 | 
				
			||||
                    { | 
					 | 
				
			||||
                        sprintf(fmtText,"%4.0f",totalYieldToday); | 
					 | 
				
			||||
                        mDisplay.print(F("today ")+String(fmtText)+F(" Wh")); | 
					 | 
				
			||||
                        mDisplay.setCursor(5,37); | 
					 | 
				
			||||
                        sprintf(fmtText,"%.1f",totalYield); | 
					 | 
				
			||||
                        mDisplay.print(F("total ")+String(fmtText)+F(" kWh")); | 
					 | 
				
			||||
                    } | 
					 | 
				
			||||
                    else | 
					 | 
				
			||||
                    { | 
					 | 
				
			||||
                        int id1=(mExtra/2)%(num_inv-1); | 
					 | 
				
			||||
                        if( pow_i[id1] ) | 
					 | 
				
			||||
                            mDisplay.print(F("#")+String(id1+1)+F("  ")+String(pow_i[id1])+F(" W")); | 
					 | 
				
			||||
                        else | 
					 | 
				
			||||
                            mDisplay.print(F("#")+String(id1+1)+F("  -----")); | 
					 | 
				
			||||
                        mDisplay.setCursor(5,37); | 
					 | 
				
			||||
                        if( pow_i[id1+1] ) | 
					 | 
				
			||||
                            mDisplay.print(F("#")+String(id1+2)+F("  ")+String(pow_i[id1+1])+F(" W")); | 
					 | 
				
			||||
                        else | 
					 | 
				
			||||
                            mDisplay.print(F("#")+String(id1+2)+F("  -----")); | 
					 | 
				
			||||
                    } | 
					 | 
				
			||||
                    if ( !(mExtra%10) && ip ) { | 
					 | 
				
			||||
                        mDisplay.setCursor(5,47); | 
					 | 
				
			||||
                        mDisplay.print(ip.toString()); | 
					 | 
				
			||||
                    } | 
					 | 
				
			||||
                    else { | 
					 | 
				
			||||
                        mDisplay.setCursor(0,47); | 
					 | 
				
			||||
                        mDisplay.print(timeStr); | 
					 | 
				
			||||
                    } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                    mDisplay.sendBuffer(); | 
					 | 
				
			||||
                } while( mDisplay.nextPage() ); | 
					 | 
				
			||||
                mExtra++; | 
					 | 
				
			||||
        #else // ENA_SSD1306
 | 
					 | 
				
			||||
            if(mUp) { | 
					 | 
				
			||||
                mRx += 2; | 
					 | 
				
			||||
                if(mRx >= 20) | 
					 | 
				
			||||
                mUp = 0; | 
					 | 
				
			||||
            } else { | 
					 | 
				
			||||
                mRx -= 2; | 
					 | 
				
			||||
                if(mRx <= 0) | 
					 | 
				
			||||
                mUp = 1; | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            int ex = 2*( mExtra % 5 ); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
            if(ucnt) { | 
					 | 
				
			||||
                mDisplay.setBrightness(63); | 
					 | 
				
			||||
                mDisplay.drawXbm(10+ex,5,8,17,bmp_arrow); | 
					 | 
				
			||||
                mDisplay.setFont(ArialMT_Plain_24); | 
					 | 
				
			||||
                sprintf(fmtText,"%3.0f",totalActual); | 
					 | 
				
			||||
                mDisplay.drawString(25+ex,0,String(fmtText)+F(" W")); | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            else | 
					 | 
				
			||||
            { | 
					 | 
				
			||||
                mDisplay.setBrightness(1); | 
					 | 
				
			||||
                mDisplay.setFont(ArialMT_Plain_24); | 
					 | 
				
			||||
                mDisplay.drawString(25+ex,0,String(F("offline"))); | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            mDisplay.setFont(ArialMT_Plain_16); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
            if (( num_inv < 2 ) || !(mExtra%2)) | 
					 | 
				
			||||
            { | 
					 | 
				
			||||
                sprintf(fmtText,"%4.0f",totalYieldToday); | 
					 | 
				
			||||
                mDisplay.drawString(5,22,F("today ")+String(fmtText)+F(" Wh")); | 
					 | 
				
			||||
                sprintf(fmtText,"%.1f",totalYield); | 
					 | 
				
			||||
                mDisplay.drawString(5,35,F("total  ")+String(fmtText)+F(" kWh")); | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            else | 
					 | 
				
			||||
            { | 
					 | 
				
			||||
                int id1=(mExtra/2)%(num_inv-1); | 
					 | 
				
			||||
                if( pow_i[id1] ) | 
					 | 
				
			||||
                    mDisplay.drawString(15,22,F("#")+String(id1+1)+F("  ")+String(pow_i[id1])+F(" W")); | 
					 | 
				
			||||
                else | 
					 | 
				
			||||
                    mDisplay.drawString(15,22,F("#")+String(id1+1)+F("  -----")); | 
					 | 
				
			||||
                if( pow_i[id1+1] ) | 
					 | 
				
			||||
                    mDisplay.drawString(15,35,F("#")+String(id1+2)+F("  ")+String(pow_i[id1+1])+F(" W")); | 
					 | 
				
			||||
                else | 
					 | 
				
			||||
                    mDisplay.drawString(15,35,F("#")+String(id1+2)+F("  -----")); | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            mDisplay.drawLine(2,23,123,23); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
            if ( (!(mExtra%10) && ip )|| (timeStr.length()<16)) | 
					 | 
				
			||||
            { | 
					 | 
				
			||||
                mDisplay.drawString(5,49,ip.toString()); | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            else | 
					 | 
				
			||||
            { | 
					 | 
				
			||||
                int w=mDisplay.getStringWidth(timeStr.c_str(),timeStr.length(),0); | 
					 | 
				
			||||
                if ( w>127 ) | 
					 | 
				
			||||
                { | 
					 | 
				
			||||
                    String tt=timeStr.substring(9,17); | 
					 | 
				
			||||
                    w=mDisplay.getStringWidth(tt.c_str(),tt.length(),0); | 
					 | 
				
			||||
                    mDisplay.drawString(127-w-mRx,49,tt); | 
					 | 
				
			||||
                } | 
					 | 
				
			||||
                else | 
					 | 
				
			||||
                    mDisplay.drawString(0,49,timeStr); | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
            mDisplay.display(); | 
					 | 
				
			||||
            mExtra++; | 
					 | 
				
			||||
        #endif | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        // private member variables
 | 
					 | 
				
			||||
        #if defined(ENA_NOKIA) | 
					 | 
				
			||||
            U8G2_PCD8544_84X48_1_4W_HW_SPI mDisplay; | 
					 | 
				
			||||
        #else // ENA_SSD1306
 | 
					 | 
				
			||||
            SSD1306Wire mDisplay; | 
					 | 
				
			||||
            int mRx; | 
					 | 
				
			||||
            char mUp; | 
					 | 
				
			||||
        #endif | 
					 | 
				
			||||
        int mExtra; | 
					 | 
				
			||||
        bool mNewPayload; | 
					 | 
				
			||||
        float mTotal[ MAX_NUM_INVERTERS ]; | 
					 | 
				
			||||
        float mToday[ MAX_NUM_INVERTERS ]; | 
					 | 
				
			||||
        uint32_t *mUtcTs; | 
					 | 
				
			||||
        int mLastHour; | 
					 | 
				
			||||
        HMSYSTEM *mSys; | 
					 | 
				
			||||
        Timezone mCE; | 
					 | 
				
			||||
}; | 
					 | 
				
			||||
#endif | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#endif /*__MONOCHROME_DISPLAY__*/ | 
					 | 
				
			||||
@ -0,0 +1,96 @@ | 
				
			|||||
 | 
					//-----------------------------------------------------------------------------
 | 
				
			||||
 | 
					// 2023 Ahoy, https://ahoydtu.de
 | 
				
			||||
 | 
					// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
 | 
				
			||||
 | 
					//-----------------------------------------------------------------------------
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#ifndef __PUB_MQTT_DEFS_H__ | 
				
			||||
 | 
					#define __PUB_MQTT_DEFS_H__ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include <Arduino.h> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					enum { | 
				
			||||
 | 
					    STR_TRUE, | 
				
			||||
 | 
					    STR_FALSE | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const char* const dict[] PROGMEM = { | 
				
			||||
 | 
					    "true", | 
				
			||||
 | 
					    "false" | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					enum { | 
				
			||||
 | 
					    MQTT_STR_LWT_CONN, | 
				
			||||
 | 
					    MQTT_STR_LWT_NOT_CONN, | 
				
			||||
 | 
					    MQTT_STR_AVAILABLE, | 
				
			||||
 | 
					    MQTT_STR_LAST_SUCCESS, | 
				
			||||
 | 
					    MQTT_STR_TOTAL, | 
				
			||||
 | 
					    MQTT_STR_GOT_TOPIC | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const char* const mqttStr[] PROGMEM = { | 
				
			||||
 | 
					    "connected", | 
				
			||||
 | 
					    "not connected", | 
				
			||||
 | 
					    "available", | 
				
			||||
 | 
					    "last_success", | 
				
			||||
 | 
					    "total", | 
				
			||||
 | 
					    "MQTT got topic: " | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					enum { | 
				
			||||
 | 
					    MQTT_UPTIME = 0, | 
				
			||||
 | 
					    MQTT_RSSI, | 
				
			||||
 | 
					    MQTT_FREE_HEAP, | 
				
			||||
 | 
					    MQTT_HEAP_FRAG, | 
				
			||||
 | 
					    MQTT_SUNRISE, | 
				
			||||
 | 
					    MQTT_SUNSET, | 
				
			||||
 | 
					    MQTT_COMM_START, | 
				
			||||
 | 
					    MQTT_COMM_STOP, | 
				
			||||
 | 
					    MQTT_DIS_NIGHT_COMM, | 
				
			||||
 | 
					    MQTT_COMM_DISABLED, | 
				
			||||
 | 
					    MQTT_COMM_DIS_TS, | 
				
			||||
 | 
					    MQTT_VERSION, | 
				
			||||
 | 
					    MQTT_DEVICE, | 
				
			||||
 | 
					    MQTT_IP_ADDR, | 
				
			||||
 | 
					    MQTT_STATUS, | 
				
			||||
 | 
					    MQTT_ALARM, | 
				
			||||
 | 
					    MQTT_ALARM_START, | 
				
			||||
 | 
					    MQTT_ALARM_END, | 
				
			||||
 | 
					    MQTT_LWT_ONLINE, | 
				
			||||
 | 
					    MQTT_LWT_OFFLINE, | 
				
			||||
 | 
					    MQTT_ACK_PWR_LMT | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const char* const subtopics[] PROGMEM = { | 
				
			||||
 | 
					    "uptime", | 
				
			||||
 | 
					    "wifi_rssi", | 
				
			||||
 | 
					    "free_heap", | 
				
			||||
 | 
						"heap_frag", | 
				
			||||
 | 
					    "sunrise", | 
				
			||||
 | 
					    "sunset", | 
				
			||||
 | 
					    "comm_start", | 
				
			||||
 | 
					    "comm_stop", | 
				
			||||
 | 
					    "dis_night_comm", | 
				
			||||
 | 
					    "comm_disabled", | 
				
			||||
 | 
					    "comm_dis_ts", | 
				
			||||
 | 
					    "version", | 
				
			||||
 | 
					    "device", | 
				
			||||
 | 
					    "ip_addr", | 
				
			||||
 | 
					    "status", | 
				
			||||
 | 
					    "alarm", | 
				
			||||
 | 
					    "alarm_start", | 
				
			||||
 | 
					    "alarm_end", | 
				
			||||
 | 
					    "connected", | 
				
			||||
 | 
					    "not_connected", | 
				
			||||
 | 
					    "ack_pwr_limit" | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					enum { | 
				
			||||
 | 
					    MQTT_SUBS_SET_TIME | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const char* const subscr[] PROGMEM = { | 
				
			||||
 | 
					    "setup/set_time" | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#endif /*__PUB_MQTT_DEFS_H__*/ | 
				
			||||
@ -1,27 +0,0 @@ | 
				
			|||||
//-----------------------------------------------------------------------------
 | 
					 | 
				
			||||
// 2022 Ahoy, https://ahoydtu.de
 | 
					 | 
				
			||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
 | 
					 | 
				
			||||
//-----------------------------------------------------------------------------
 | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#ifndef __AHOY_TIMER_H__ | 
					 | 
				
			||||
#define __AHOY_TIMER_H__ | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#include <Arduino.h> | 
					 | 
				
			||||
 | 
					 | 
				
			||||
namespace ah { | 
					 | 
				
			||||
    inline bool checkTicker(uint32_t *ticker, uint32_t interval) { | 
					 | 
				
			||||
        uint32_t mil = millis(); | 
					 | 
				
			||||
        if(mil >= *ticker) { | 
					 | 
				
			||||
            *ticker = mil + interval; | 
					 | 
				
			||||
            return true; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
        else if(mil < (*ticker - interval)) { | 
					 | 
				
			||||
            *ticker = mil + interval; | 
					 | 
				
			||||
            return true; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        return false; | 
					 | 
				
			||||
    } | 
					 | 
				
			||||
} | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#endif /*__AHOY_TIMER_H__*/ | 
					 | 
				
			||||
@ -1,33 +0,0 @@ | 
				
			|||||
//-----------------------------------------------------------------------------
 | 
					 | 
				
			||||
// 2022 Ahoy, https://ahoydtu.de
 | 
					 | 
				
			||||
// Lukas Pusch, lukas@lpusch.de
 | 
					 | 
				
			||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
 | 
					 | 
				
			||||
//-----------------------------------------------------------------------------
 | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#ifndef __HANDLER_H__ | 
					 | 
				
			||||
#define __HANDLER_H__ | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#include <memory> | 
					 | 
				
			||||
#include <functional> | 
					 | 
				
			||||
#include <list> | 
					 | 
				
			||||
 | 
					 | 
				
			||||
template<class TYPE> | 
					 | 
				
			||||
class Handler { | 
					 | 
				
			||||
    public: | 
					 | 
				
			||||
        Handler() {} | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        void addListener(TYPE f) { | 
					 | 
				
			||||
            mList.push_back(f); | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        /*virtual void notify(void) {
 | 
					 | 
				
			||||
            for(typename std::list<TYPE>::iterator it = mList.begin(); it != mList.end(); ++it) { | 
					 | 
				
			||||
               (*it)(); | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
        }*/ | 
					 | 
				
			||||
 | 
					 | 
				
			||||
    protected: | 
					 | 
				
			||||
        std::list<TYPE> mList; | 
					 | 
				
			||||
}; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#endif /*__HANDLER_H__*/ | 
					 | 
				
			||||
@ -1,110 +0,0 @@ | 
				
			|||||
//-----------------------------------------------------------------------------
 | 
					 | 
				
			||||
// 2022 Ahoy, https://ahoydtu.de
 | 
					 | 
				
			||||
// Lukas Pusch, lukas@lpusch.de
 | 
					 | 
				
			||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
 | 
					 | 
				
			||||
//-----------------------------------------------------------------------------
 | 
					 | 
				
			||||
#ifndef __LIST_H__ | 
					 | 
				
			||||
#define __LIST_H__ | 
					 | 
				
			||||
 | 
					 | 
				
			||||
template<class T, class... Args> | 
					 | 
				
			||||
struct node_s { | 
					 | 
				
			||||
    typedef T dT; | 
					 | 
				
			||||
    node_s *pre; | 
					 | 
				
			||||
    node_s *nxt; | 
					 | 
				
			||||
    uint8_t id; | 
					 | 
				
			||||
    dT d; | 
					 | 
				
			||||
    node_s() : pre(NULL), nxt(NULL), d() {} | 
					 | 
				
			||||
    node_s(Args... args) : id(0), pre(NULL), nxt(NULL), d(args...) {} | 
					 | 
				
			||||
}; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
template<int MAX_NUM, class T, class... Args> | 
					 | 
				
			||||
class llist { | 
					 | 
				
			||||
    typedef node_s<T, Args...> elmType; | 
					 | 
				
			||||
    typedef T dataType; | 
					 | 
				
			||||
    public: | 
					 | 
				
			||||
        llist() : root(mPool) { | 
					 | 
				
			||||
            root = NULL; | 
					 | 
				
			||||
            elmType *p = mPool; | 
					 | 
				
			||||
            for(uint32_t i = 0; i < MAX_NUM; i++) { | 
					 | 
				
			||||
                p->id = i; | 
					 | 
				
			||||
                p++; | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            mFill = mMax = 0; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        elmType *add(Args... args) { | 
					 | 
				
			||||
            elmType *p = root, *t; | 
					 | 
				
			||||
            if(NULL == (t = getFreeNode())) | 
					 | 
				
			||||
                return NULL; | 
					 | 
				
			||||
            if(++mFill > mMax) | 
					 | 
				
			||||
                mMax = mFill; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
            if(NULL == root) { | 
					 | 
				
			||||
                p = root = t; | 
					 | 
				
			||||
                p->pre = p; | 
					 | 
				
			||||
                p->nxt = p; | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            else { | 
					 | 
				
			||||
                p = root->pre; | 
					 | 
				
			||||
                t->pre = p; | 
					 | 
				
			||||
                p->nxt->pre = t; | 
					 | 
				
			||||
                t->nxt = p->nxt; | 
					 | 
				
			||||
                p->nxt = t; | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            t->d = dataType(args...); | 
					 | 
				
			||||
            return p; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        elmType *getFront() { | 
					 | 
				
			||||
            return root; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        elmType *get(elmType *p) { | 
					 | 
				
			||||
            p = p->nxt; | 
					 | 
				
			||||
            return (p == root) ? NULL : p; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        elmType *rem(elmType *p) { | 
					 | 
				
			||||
            if(NULL == p) | 
					 | 
				
			||||
                return NULL; | 
					 | 
				
			||||
            elmType *t = p->nxt; | 
					 | 
				
			||||
            p->nxt->pre = p->pre; | 
					 | 
				
			||||
            p->pre->nxt = p->nxt; | 
					 | 
				
			||||
            if((root == p) && (p->nxt == p)) | 
					 | 
				
			||||
                root = NULL; | 
					 | 
				
			||||
            else | 
					 | 
				
			||||
                root = p->nxt; | 
					 | 
				
			||||
            p->nxt = NULL; | 
					 | 
				
			||||
            p->pre = NULL; | 
					 | 
				
			||||
            p = NULL; | 
					 | 
				
			||||
            mFill--; | 
					 | 
				
			||||
            return (NULL == root) ? NULL : ((t == root) ? NULL : t); | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        uint16_t getFill(void) { | 
					 | 
				
			||||
            return mFill; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        uint16_t getMaxFill(void) { | 
					 | 
				
			||||
            return mMax; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
    protected: | 
					 | 
				
			||||
        elmType *root; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
    private: | 
					 | 
				
			||||
        elmType *getFreeNode(void) { | 
					 | 
				
			||||
            elmType *n = mPool; | 
					 | 
				
			||||
            for(uint32_t i = 0; i < MAX_NUM; i++) { | 
					 | 
				
			||||
                if(NULL == n->nxt) | 
					 | 
				
			||||
                    return n; | 
					 | 
				
			||||
                n++; | 
					 | 
				
			||||
            } | 
					 | 
				
			||||
            return NULL; | 
					 | 
				
			||||
        } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        elmType mPool[MAX_NUM]; | 
					 | 
				
			||||
        uint16_t mFill, mMax; | 
					 | 
				
			||||
}; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
#endif /*__LIST_H__*/ | 
					 | 
				
			||||
@ -0,0 +1,57 @@ | 
				
			|||||
 | 
					<!doctype html> | 
				
			||||
 | 
					<html> | 
				
			||||
 | 
					    <head> | 
				
			||||
 | 
					        <title>About</title> | 
				
			||||
 | 
					        {#HTML_HEADER} | 
				
			||||
 | 
					    </head> | 
				
			||||
 | 
					    <body> | 
				
			||||
 | 
					        {#HTML_NAV} | 
				
			||||
 | 
					        <div id="wrapper"> | 
				
			||||
 | 
					            <div id="content"> | 
				
			||||
 | 
					                <div class="my-3"><h2>About AhoyDTU</h2></div> | 
				
			||||
 | 
					                <div class="my-3"> | 
				
			||||
 | 
					                    <div class="row my-3 head"> | 
				
			||||
 | 
					                        <div class="p-2">Used Libraries</div> | 
				
			||||
 | 
					                    </div> | 
				
			||||
 | 
					                    <div class="row"><a href="https://github.com/bertmelis/espMqttClient" target="_blank">bertmelis/espMqttClient</a></div> | 
				
			||||
 | 
					                    <div class="row"><a href="https://github.com/yubox-node-org/ESPAsyncWebServer" target="_blank">yubox-node-org/ESPAsyncWebServer</a></div> | 
				
			||||
 | 
					                    <div class="row"><a href="https://github.com/bblanchon/ArduinoJson" target="_blank">bblanchon/ArduinoJson</a></div> | 
				
			||||
 | 
					                    <div class="row"><a href="https://github.com/nrf24/RF24" target="_blank">nrf24/RF24</a></div> | 
				
			||||
 | 
					                    <div class="row"><a href="https://github.com/paulstoffregen/Time" target="_blank">paulstoffregen/Time</a></div> | 
				
			||||
 | 
					                    <div class="row"><a href="https://github.com/olikraus/U8g2" target="_blank">olikraus/U8g2</a></div> | 
				
			||||
 | 
					                    <div class="row"><a href="https://github.com/zinggjm/GxEPD2" target="_blank">zinggjm/GxEPD2</a></div> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                    <div class="row my-3 head"> | 
				
			||||
 | 
					                        <div class="p-2">Contact Information</div> | 
				
			||||
 | 
					                    </div> | 
				
			||||
 | 
					                    <div class="row"> | 
				
			||||
 | 
					                        <div class="col-5 col-sm-3">Github Repository</div> | 
				
			||||
 | 
					                        <div class="col-7 col-sm-9"><a href="https://github.com/lumapu/ahoy">https://github.com/lumapu/ahoy</a></div> | 
				
			||||
 | 
					                    </div> | 
				
			||||
 | 
					                    <div class="row"> | 
				
			||||
 | 
					                        <div class="col-5 col-sm-3">Discord Chat</div> | 
				
			||||
 | 
					                        <div class="col-7 col-sm-9"><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></div> | 
				
			||||
 | 
					                    </div> | 
				
			||||
 | 
					                    <div class="row"> | 
				
			||||
 | 
					                        <div class="col-5 col-sm-3">E-Mail</div> | 
				
			||||
 | 
					                        <div class="col-7 col-sm-9"><a href="mailto:contact@ahoydtu.de">contact@ahoydtu.de</a></div> | 
				
			||||
 | 
					                    </div> | 
				
			||||
 | 
					                </div> | 
				
			||||
 | 
					            </div> | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					        {#HTML_FOOTER} | 
				
			||||
 | 
					        <script type="text/javascript"> | 
				
			||||
 | 
					            function parseGeneric(obj) { | 
				
			||||
 | 
					                parseNav(obj); | 
				
			||||
 | 
					                parseESP(obj); | 
				
			||||
 | 
					                parseRssi(obj); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					            function parse(obj) { | 
				
			||||
 | 
					                if(null != obj) { | 
				
			||||
 | 
					                    parseGeneric(obj["generic"]); | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					            getAjax("/api/html/save", parse); | 
				
			||||
 | 
					        </script> | 
				
			||||
 | 
					    </body> | 
				
			||||
 | 
					</html> | 
				
			||||
@ -0,0 +1,27 @@ | 
				
			|||||
 | 
					:root { | 
				
			||||
 | 
					    --bg: #fff; | 
				
			||||
 | 
					    --fg: #000; | 
				
			||||
 | 
					    --fg2: #fff; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    --info: #0000dd; | 
				
			||||
 | 
					    --warn: #ff7700; | 
				
			||||
 | 
					    --success: #009900; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    --input-bg: #eee; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    --nav-bg: #333; | 
				
			||||
 | 
					    --primary: #006ec0; | 
				
			||||
 | 
					    --primary-hover: #044e86; | 
				
			||||
 | 
					    --secondary: #0072c8; | 
				
			||||
 | 
					    --nav-active: #555; | 
				
			||||
 | 
					    --footer-bg: #282828; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    --total-head-title: #8e5903; | 
				
			||||
 | 
					    --total-bg: #b06e04; | 
				
			||||
 | 
					    --iv-head-title: #1c6800; | 
				
			||||
 | 
					    --iv-head-bg: #32b004; | 
				
			||||
 | 
					    --ch-head-title: #003c80; | 
				
			||||
 | 
					    --ch-head-bg: #006ec0; | 
				
			||||
 | 
					    --ts-head: #333; | 
				
			||||
 | 
					    --ts-bg: #555; | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,27 @@ | 
				
			|||||
 | 
					:root { | 
				
			||||
 | 
					    --bg: #222; | 
				
			||||
 | 
					    --fg: #ccc; | 
				
			||||
 | 
					    --fg2: #fff; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    --info: #0072c8; | 
				
			||||
 | 
					    --warn: #ffaa00; | 
				
			||||
 | 
					    --success: #00bb00; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    --input-bg: #333; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    --nav-bg: #333; | 
				
			||||
 | 
					    --primary: #004d87; | 
				
			||||
 | 
					    --primary-hover: #023155; | 
				
			||||
 | 
					    --secondary: #0072c8; | 
				
			||||
 | 
					    --nav-active: #555; | 
				
			||||
 | 
					    --footer-bg: #282828; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    --total-head-title: #555511; | 
				
			||||
 | 
					    --total-bg: #666622; | 
				
			||||
 | 
					    --iv-head-title: #115511; | 
				
			||||
 | 
					    --iv-head-bg: #226622; | 
				
			||||
 | 
					    --ch-head-title: #112255; | 
				
			||||
 | 
					    --ch-head-bg: #223366; | 
				
			||||
 | 
					    --ts-head: #333; | 
				
			||||
 | 
					    --ts-bg: #555; | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,16 @@ | 
				
			|||||
 | 
					<div id="footer"> | 
				
			||||
 | 
					        <div class="left"> | 
				
			||||
 | 
					        <a href="https://ahoydtu.de" target="_blank">AhoyDTU © 2023</a> | 
				
			||||
 | 
					        <ul> | 
				
			||||
 | 
					            <li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li> | 
				
			||||
 | 
					            <li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li> | 
				
			||||
 | 
					        </ul> | 
				
			||||
 | 
					    </div> | 
				
			||||
 | 
					    <div class="right"> | 
				
			||||
 | 
					        <ul> | 
				
			||||
 | 
					            <li>{#VERSION_GIT}</li> | 
				
			||||
 | 
					            <li id="esp_type"></li> | 
				
			||||
 | 
					            <li><a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed" target="_blank" >CC BY-NC-SA 4.0</a></li> | 
				
			||||
 | 
					        </ul> | 
				
			||||
 | 
					    </div> | 
				
			||||
 | 
					</div> | 
				
			||||
@ -0,0 +1,5 @@ | 
				
			|||||
 | 
					<link rel="stylesheet" type="text/css" href="colors.css"/> | 
				
			||||
 | 
					<link rel="stylesheet" type="text/css" href="style.css"/> | 
				
			||||
 | 
					<meta name="viewport" content="width=device-width, initial-scale=1"> | 
				
			||||
 | 
					<meta charset="UTF-8"> | 
				
			||||
 | 
					<script type="text/javascript" src="api.js"></script> | 
				
			||||
@ -0,0 +1,24 @@ | 
				
			|||||
 | 
					<div class="topnav"> | 
				
			||||
 | 
					    <a href="/" class="title">AhoyDTU</a> | 
				
			||||
 | 
					    <a href="javascript:void(0);" class="icon" onclick="topnav()"> | 
				
			||||
 | 
					        <span></span> | 
				
			||||
 | 
					        <span></span> | 
				
			||||
 | 
					        <span></span> | 
				
			||||
 | 
					    </a> | 
				
			||||
 | 
					    <div id="topnav" class="mobile"> | 
				
			||||
 | 
					        <a id="nav3" class="hide" href="/live">Live</a> | 
				
			||||
 | 
					        <a id="nav4" class="hide" href="/serial">Serial / Control</a> | 
				
			||||
 | 
					        <a id="nav5" class="hide" href="/setup">Settings</a> | 
				
			||||
 | 
					        <span class="seperator"></span> | 
				
			||||
 | 
					        <a id="nav6" class="hide" href="/update">Update</a> | 
				
			||||
 | 
					        <a id="nav7" class="hide" href="/system">System</a> | 
				
			||||
 | 
					        <span class="seperator"></span> | 
				
			||||
 | 
					        <a id="nav8" href="/api" target="_blank">REST API</a> | 
				
			||||
 | 
					        <a id="nav9" href="https://ahoydtu.de" target="_blank">Documentation</a> | 
				
			||||
 | 
					        <a id="nav10" href="/about">About</a> | 
				
			||||
 | 
					        <span class="seperator"></span> | 
				
			||||
 | 
					        <a id="nav0" class="hide" href="/login">Login</a> | 
				
			||||
 | 
					        <a id="nav1" class="hide" href="/logout">Logout</a> | 
				
			||||
 | 
					    </div> | 
				
			||||
 | 
					    <div id="wifiicon" class="info"></div> | 
				
			||||
 | 
					</div> | 
				
			||||
@ -0,0 +1,51 @@ | 
				
			|||||
 | 
					<!doctype html> | 
				
			||||
 | 
					<html> | 
				
			||||
 | 
					    <head> | 
				
			||||
 | 
					        <title>Save</title> | 
				
			||||
 | 
					        {#HTML_HEADER} | 
				
			||||
 | 
					    </head> | 
				
			||||
 | 
					    <body> | 
				
			||||
 | 
					        {#HTML_NAV} | 
				
			||||
 | 
					        <div id="wrapper"> | 
				
			||||
 | 
					            <div id="content"> | 
				
			||||
 | 
					                <div id="html" class="mt-3 mb-3"></div> | 
				
			||||
 | 
					            </div> | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					        {#HTML_FOOTER} | 
				
			||||
 | 
					        <script type="text/javascript"> | 
				
			||||
 | 
					            function parseGeneric(obj) { | 
				
			||||
 | 
					                parseNav(obj); | 
				
			||||
 | 
					                parseESP(obj); | 
				
			||||
 | 
					                parseRssi(obj); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            function parseHtml(obj) { | 
				
			||||
 | 
					                var html = ""; | 
				
			||||
 | 
					                if(obj.pending) | 
				
			||||
 | 
					                    html = "saving settings ..."; | 
				
			||||
 | 
					                else { | 
				
			||||
 | 
					                    if(obj.success) | 
				
			||||
 | 
					                        html = "settings successfully saved"; | 
				
			||||
 | 
					                    else | 
				
			||||
 | 
					                        html = "failed saving settings"; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                    var meta = document.createElement('meta'); | 
				
			||||
 | 
					                    meta.httpEquiv = "refresh" | 
				
			||||
 | 
					                    meta.content = 1 + "; URL=/setup"; | 
				
			||||
 | 
					                    document.getElementsByTagName('head')[0].appendChild(meta); | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					                document.getElementById("html").innerHTML = html; | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            function parse(obj) { | 
				
			||||
 | 
					                if(null != obj) { | 
				
			||||
 | 
					                    parseGeneric(obj["generic"]); | 
				
			||||
 | 
					                    parseHtml(obj); | 
				
			||||
 | 
					                    window.setInterval("getAjax('/api/html/save', parse)", 1100); | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            getAjax("/api/html/save", parse); | 
				
			||||
 | 
					        </script> | 
				
			||||
 | 
					    </body> | 
				
			||||
 | 
					</html> | 
				
			||||
								
									
										File diff suppressed because it is too large
									
								
							
						
					| 
		 After Width: | Height: | Size: 1.5 MiB  | 
								
									Binary file not shown.
								
							
						
					
								
									Binary file not shown.
								
							
						
					@ -0,0 +1,30 @@ | 
				
			|||||
 | 
					# EKD ESPNRF Case | 
				
			||||
 | 
					<picture> | 
				
			||||
 | 
					 <source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/10756851/221722400-eefc8790-6283-4c00-a82b-e2699cae72d6.jpg"> | 
				
			||||
 | 
					 <source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/10756851/221722400-eefc8790-6283-4c00-a82b-e2699cae72d6.jpg"> | 
				
			||||
 | 
					 <img alt="EKD ESPNRF Case" src="https://user-images.githubusercontent.com/10756851/221722400-eefc8790-6283-4c00-a82b-e2699cae72d6.jpg"> | 
				
			||||
 | 
					</picture> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					### Print Details: | 
				
			||||
 | 
					- Print with 0.2 mm Layers | 
				
			||||
 | 
					- use 100% infill | 
				
			||||
 | 
					- no supports needed | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					### Things needed: | 
				
			||||
 | 
					- 3D Printer | 
				
			||||
 | 
					- Wemos D1 Mini (format style) | 
				
			||||
 | 
					- NRF24L01+ Board  | 
				
			||||
 | 
					- ~ 15cm wire | 
				
			||||
 | 
					- Soldering Iron + Solder | 
				
			||||
 | 
					- Suction pump to free the NRF Board from the pins.  | 
				
			||||
 | 
					(Solder wick works too but i do not recommend =) | 
				
			||||
 | 
					- If you want to go for a wall mounted device, add some screws. | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					Unsolder the Pins from the NRF Board and use short wires instead. I went this way to keep the design as flat as possible. | 
				
			||||
 | 
					<picture> | 
				
			||||
 | 
					 <img alt="EKD ESPNRF Case" src="https://user-images.githubusercontent.com/10756851/221722732-1ae9162c-ef77-492e-babf-075045b81f69.png"> | 
				
			||||
 | 
					</picture> | 
				
			||||
 | 
					If you got questions or need help feel free to ask on discord. | 
				
			||||
 | 
					or find me on github.com/subdancer | 
				
			||||
 | 
					Cheers. | 
				
			||||
@ -0,0 +1,15 @@ | 
				
			|||||
 | 
					############################ | 
				
			||||
 | 
					# build executable binary | 
				
			||||
 | 
					############################ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					FROM python:slim-bullseye | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					COPY . /hoymiles | 
				
			||||
 | 
					WORKDIR /hoymiles | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					RUN python3 -m pip install pyrf24 influxdb_client && \ | 
				
			||||
 | 
					python3 -m pip list #watch for RF24 module - if its there its installed | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					RUN pip install crcmod pyyaml paho-mqtt SunTimes | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					CMD python3 -um hoymiles --log-transactions --verbose --config /etc/ahoy.yml | 
				
			||||
					Loading…
					
					
				
		Reference in new issue