├── .gitignore ├── include ├── LED.h ├── Macros.h ├── Config.h ├── README ├── HA.h ├── GxEPD2_selection_check.h └── GxEPD2_display_selection_new_style.h ├── test └── README ├── src ├── RTTTL.ino ├── LED.ino ├── Buzzer.ino ├── ADC.ino ├── OTA.ino ├── Config.ino ├── Environment.ino ├── Detector.ino ├── Geiger.ino ├── WiFi.ino ├── PWM.ino ├── Time.ino ├── HA.ino ├── MQTT.ino ├── EPD.ino └── Webserver.ino ├── lib └── README ├── platformio.ini └── data └── plot.html /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /include/LED.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | void led_setup(); 5 | void led_set_adv(uint8_t n, uint8_t r, uint8_t g, uint8_t b, bool commit); 6 | void led_set(uint8_t n, uint8_t r, uint8_t g, uint8_t b); 7 | void led_set_inhibit(bool state); 8 | bool led_loop(); 9 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /src/RTTTL.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | MD_RTTTLParser Tone; 6 | MD_MusicTable Table; 7 | 8 | void rtttl_cb (uint8_t octave, uint8_t noteId, uint32_t duration, bool activate) 9 | { 10 | if(activate) 11 | { 12 | if(Table.findNoteOctave(noteId, octave)) 13 | { 14 | buzz_on(Table.getFrequency()); 15 | } 16 | } 17 | else 18 | { 19 | buzz_off(); 20 | } 21 | } 22 | 23 | void rtttl_setup() 24 | { 25 | Tone.begin(); 26 | Tone.setCallback(&rtttl_cb); 27 | } 28 | 29 | bool rtttl_loop() 30 | { 31 | Tone.run(); 32 | 33 | return false; 34 | } 35 | 36 | void rtttl_play(const char *rtttl) 37 | { 38 | Tone.setTune(rtttl); 39 | } 40 | -------------------------------------------------------------------------------- /include/Macros.h: -------------------------------------------------------------------------------- 1 | #ifndef __MACROS_H__ 2 | #define __MACROS_H__ 3 | 4 | // #define min(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) 5 | // #define max(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) 6 | #define coerce(val, min, max) \ 7 | do \ 8 | { \ 9 | if ((val) > (max)) \ 10 | { \ 11 | val = max; \ 12 | } \ 13 | else if ((val) < (min)) \ 14 | { \ 15 | val = min; \ 16 | } \ 17 | } while (0) 18 | #define xstr(s) str(s) 19 | #define str(s) #s 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/LED.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define LED_GPIO 4 4 | #define LED_COUNT 6 5 | 6 | CRGB leds[LED_COUNT]; 7 | 8 | bool led_inhibit = false; 9 | 10 | void led_setup() 11 | { 12 | FastLED.addLeds(leds, LED_COUNT); 13 | for(int num = 0; num < LED_COUNT; num++) 14 | { 15 | led_set_adv(num, 0, 0, 0, false); 16 | } 17 | led_set_adv(0, 0, 0, 16, true); 18 | } 19 | 20 | void led_set_adv(uint8_t n, uint8_t r, uint8_t g, uint8_t b, bool commit) 21 | { 22 | if(led_inhibit) 23 | { 24 | return; 25 | } 26 | 27 | leds[n] = CRGB(r, g, b); 28 | 29 | if (commit) 30 | { 31 | FastLED.show(); 32 | } 33 | } 34 | 35 | void led_set(uint8_t n, uint8_t r, uint8_t g, uint8_t b) 36 | { 37 | return led_set_adv(n, r, g, b, true); 38 | } 39 | 40 | void led_set_inhibit(bool state) 41 | { 42 | led_inhibit = state; 43 | } 44 | 45 | bool led_loop() 46 | { 47 | return false; 48 | } 49 | -------------------------------------------------------------------------------- /src/Buzzer.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "Config.h" 3 | 4 | #define BUZZER_LEDC 4 5 | #define BUZZER_GPIO 32 6 | 7 | #define PWM_BITS 9 8 | #define PWM_PCT(x) ((uint32_t)((100.0f - x) * ((1UL << (PWM_BITS)) - 1) / 100.0f)) 9 | 10 | void buz_setup() 11 | { 12 | pinMode(BUZZER_GPIO, OUTPUT); 13 | digitalWrite(BUZZER_GPIO, LOW); 14 | ledcAttachPin(BUZZER_GPIO, BUZZER_LEDC); 15 | // ledcSetup(BUZZER_LEDC, current_config.buzz_freq, 12); 16 | // ledcWrite(BUZZER_LEDC, 0); 17 | } 18 | 19 | bool buz_loop() 20 | { 21 | return false; 22 | } 23 | 24 | void buzz_on(uint32_t freq) 25 | { 26 | ledcSetup(BUZZER_LEDC, freq, 12); 27 | ledcWrite(BUZZER_LEDC, PWM_PCT(50)); 28 | } 29 | 30 | void buzz_off() 31 | { 32 | ledcWrite(BUZZER_LEDC, 0); 33 | } 34 | 35 | void buzz_beep(uint32_t freq, uint32_t duration) 36 | { 37 | buzz_on(freq); 38 | delay(duration); 39 | buzz_off(); 40 | } 41 | 42 | void buz_tick() 43 | { 44 | buzz_beep(current_config.buzz_freq, current_config.buzz_length); 45 | } 46 | -------------------------------------------------------------------------------- /include/Config.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H__ 2 | #define __CONFIG_H__ 3 | 4 | #define CONFIG_SOFTAPNAME "esp32-config" 5 | #define CONFIG_OTANAME "Geiger" 6 | 7 | #define CONFIG_MAGIC 0xE1AAFF0A 8 | typedef struct 9 | { 10 | uint32_t magic; 11 | 12 | char hostname[32]; 13 | char wifi_ssid[32]; 14 | char wifi_password[32]; 15 | 16 | char mqtt_server[32]; 17 | int mqtt_port; 18 | char mqtt_user[32]; 19 | char mqtt_password[32]; 20 | char mqtt_client[32]; 21 | 22 | float conv_usv_per_bq; 23 | float voltage_target; 24 | float voltage_min; 25 | float voltage_max; 26 | uint32_t voltage_avg; 27 | float adc_corr; 28 | float pwm_value; 29 | float pwm_pid_i; 30 | uint32_t pwm_freq; 31 | uint32_t pwm_freq_min; 32 | uint32_t pwm_freq_max; 33 | uint32_t verbose; 34 | uint32_t idle_color; 35 | uint32_t elevated_color; 36 | uint32_t flash_color; 37 | uint32_t elevated_level; 38 | uint32_t buzz_length; 39 | uint32_t buzz_freq; 40 | uint32_t mqtt_publish; 41 | } t_cfg; 42 | 43 | 44 | extern t_cfg current_config; 45 | 46 | 47 | #endif -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /src/ADC.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "Config.h" 4 | 5 | #define ADC_GPIO 34 6 | 7 | //#define R3 (14700000.0f) 8 | //#define R3 (13800000.0f) /* the 14.7M falls in resistance with raising current */ 9 | #define R3 (15800000.0f) /* the 14.7M falls in resistance with raising current */ 10 | #define R4 (82000.0f) 11 | #define RSUM (R3 + R4) 12 | 13 | #define ADC_VADC(adc_raw) ((adc_raw)*3.3f / 4096) 14 | #define ADC_VHV(v_adc) ((v_adc)*RSUM / R4 * current_config.adc_corr) 15 | 16 | float adc_raw = 0; 17 | float adc_vadc = 0.0f; 18 | float adc_voltage = 0.0f; 19 | float adc_voltage_avg = 0.0f; 20 | 21 | void adc_setup() 22 | { 23 | analogReadResolution(12); 24 | } 25 | 26 | float adc_get_voltage() 27 | { 28 | return adc_voltage_avg; 29 | } 30 | 31 | void adc_reset_voltage() 32 | { 33 | adc_voltage_avg = 0; 34 | } 35 | 36 | bool adc_loop() 37 | { 38 | uint32_t curTime = millis(); 39 | static uint32_t nextTime = 0; 40 | 41 | if(curTime > nextTime || !pwm_is_stable()) 42 | { 43 | float raw = 0; 44 | for (int sample = 0; sample < current_config.voltage_avg; sample++) 45 | { 46 | raw += analogRead(ADC_GPIO); 47 | } 48 | adc_raw = raw / current_config.voltage_avg; 49 | adc_vadc = ADC_VADC(adc_raw); 50 | adc_voltage = ADC_VHV(adc_vadc); 51 | 52 | if(adc_voltage_avg > 0.1f) 53 | { 54 | adc_voltage_avg = (3 * adc_voltage_avg + adc_voltage) / 4; 55 | } 56 | else 57 | { 58 | adc_voltage_avg = adc_voltage; 59 | } 60 | 61 | nextTime = curTime + 500; 62 | 63 | return true; 64 | } 65 | 66 | return false; 67 | } 68 | -------------------------------------------------------------------------------- /src/OTA.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | bool ota_active = false; 5 | bool ota_setup_done = false; 6 | uint32_t ota_offtime = 0; 7 | 8 | void ota_setup() 9 | { 10 | if(ota_setup_done) 11 | { 12 | ota_enable(); 13 | return; 14 | } 15 | ArduinoOTA.setHostname(CONFIG_OTANAME); 16 | 17 | ArduinoOTA.onStart([]() { 18 | Serial.printf("[OTA] starting\n"); 19 | led_set(0, 255, 0, 255); 20 | ota_active = true; 21 | ota_offtime = millis() + 600000; 22 | }) 23 | .onEnd([]() { 24 | ota_active = false; 25 | }) 26 | .onProgress([](unsigned int progress, unsigned int total) { 27 | led_set(0, 255 - (progress / (total / 255)), 0, (progress / (total / 255))); 28 | }) 29 | .onError([](ota_error_t error) { 30 | Serial.printf("Error[%u]: ", error); 31 | if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); 32 | else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); 33 | else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); 34 | else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); 35 | else if (error == OTA_END_ERROR) Serial.println("End Failed"); 36 | }); 37 | 38 | ArduinoOTA.begin(); 39 | 40 | Serial.printf("[OTA] Setup finished\n"); 41 | ota_setup_done = true; 42 | ota_enable(); 43 | } 44 | 45 | void ota_enable() 46 | { 47 | Serial.printf("[OTA] Enabled\n"); 48 | ota_offtime = millis() + 600000; 49 | } 50 | 51 | bool ota_enabled() 52 | { 53 | return (ota_offtime > millis() || ota_active); 54 | } 55 | 56 | bool ota_loop() 57 | { 58 | if(ota_enabled()) 59 | { 60 | ArduinoOTA.handle(); 61 | } 62 | 63 | return ota_active; 64 | } 65 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | 2 | [env:Geiger_Local] 3 | platform = espressif32 4 | board = pico32 5 | framework = arduino 6 | board_build.f_cpu = 240000000L 7 | monitor_speed = 115200 8 | build_flags = !bash -c "echo -Isrc -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_PORT=Serial -DPIO_SRC_REVNUM=$(git rev-list --count HEAD) -DPIO_SRC_REV=$(git rev-parse --short HEAD)" 9 | board_build.flash_mode = qio 10 | monitor_filters = esp8266_exception_decoder, default 11 | lib_deps = 12 | fastled/FastLED@^3.5.0 13 | knolleary/PubSubClient@^2.8 14 | adafruit/Adafruit BusIO@^1.11.3 15 | adafruit/Adafruit GFX Library@^1.10.14 16 | adafruit/Adafruit BME280 Library@^2.2.2 17 | adafruit/Adafruit CCS811 Library@^1.1.1 18 | olikraus/U8g2_for_Adafruit_GFX@^1.8.0 19 | suculent/ESP32httpUpdate@^2.1.145 20 | zinggjm/GxEPD2@^1.4.6 21 | https://github.com/MajicDesigns/MD_MusicTable 22 | https://github.com/MajicDesigns/MD_RTTTLParser 23 | 24 | 25 | [env:Geiger_OTA] 26 | platform = espressif32 27 | board = pico32 28 | framework = arduino 29 | board_build.f_cpu = 240000000L 30 | monitor_speed = 115200 31 | build_flags = !bash -c "echo -Isrc -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_PORT=Serial -DPIO_SRC_REVNUM=$(git rev-list --count HEAD) -DPIO_SRC_REV=$(git rev-parse --short HEAD)" 32 | board_build.flash_mode = qio 33 | lib_deps = 34 | fastled/FastLED@^3.5.0 35 | knolleary/PubSubClient@^2.8 36 | adafruit/Adafruit BusIO@^1.11.3 37 | adafruit/Adafruit GFX Library@^1.10.14 38 | adafruit/Adafruit BME280 Library@^2.2.2 39 | adafruit/Adafruit CCS811 Library@^1.1.1 40 | olikraus/U8g2_for_Adafruit_GFX@^1.8.0 41 | suculent/ESP32httpUpdate@^2.1.145 42 | zinggjm/GxEPD2@^1.4.6 43 | https://github.com/MajicDesigns/MD_MusicTable 44 | https://github.com/MajicDesigns/MD_RTTTLParser 45 | upload_protocol = espota 46 | upload_port = 192.168.1.56 47 | 48 | -------------------------------------------------------------------------------- /include/HA.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define MAX_LEN 32 4 | #define MAX_ENTITIES 48 5 | 6 | typedef enum 7 | { 8 | ha_unused = 0, 9 | /* https://www.home-assistant.io/integrations/text.mqtt/ */ 10 | ha_text, 11 | /* https://www.home-assistant.io/integrations/sensor.mqtt/ */ 12 | ha_sensor, 13 | /* https://www.home-assistant.io/integrations/number.mqtt/ */ 14 | ha_number, 15 | /* https://www.home-assistant.io/integrations/button.mqtt/ */ 16 | ha_button, 17 | /* https://www.home-assistant.io/integrations/select.mqtt/ */ 18 | ha_select, 19 | /* https://www.home-assistant.io/integrations/binary_sensor.mqtt/ */ 20 | ha_binary_sensor, 21 | /* https://www.home-assistant.io/integrations/ha_light.mqtt/ */ 22 | ha_light 23 | } t_ha_device_type; 24 | 25 | typedef struct s_ha_entity t_ha_entity; 26 | 27 | struct s_ha_entity 28 | { 29 | t_ha_device_type type; 30 | 31 | const char *name; 32 | const char *id; 33 | 34 | /* used by: sensor */ 35 | const char *unit_of_meas; 36 | /* used by: sensor */ 37 | const char *val_tpl; 38 | /* used by: sensor */ 39 | const char *dev_class; 40 | /* used by: sensor */ 41 | const char *state_class; 42 | /* used by: button, number, text */ 43 | const char *cmd_t; 44 | /* used by: sensor, binary_sensor, number, text */ 45 | const char *stat_t; 46 | /* used by: light */ 47 | const char *rgb_t; 48 | const char *rgbw_t; 49 | /* used by: switch, comma separated */ 50 | const char *options; 51 | /* used by: number */ 52 | float min; 53 | /* used by: number */ 54 | float max; 55 | /* used by: number */ 56 | const char *mode; 57 | /* icon */ 58 | const char *ic; 59 | /* entity_category */ 60 | const char *ent_cat; 61 | 62 | /* used by: light */ 63 | const char *fx_cmd_t; 64 | /* used by: light */ 65 | const char *fx_stat_t; 66 | /* used by: light, comma separated */ 67 | const char *fx_list; 68 | 69 | /* alternative client name */ 70 | const char *alt_name; 71 | 72 | void (*received)(const t_ha_entity *, void *, const char *); 73 | void *received_ctx; 74 | void (*rgb_received)(const t_ha_entity *, void *, const char *); 75 | void *rgb_received_ctx; 76 | void (*fx_received)(const t_ha_entity *, void *, const char *); 77 | void *fx_received_ctx; 78 | void (*transmit)(const t_ha_entity *, void *); 79 | void *transmit_ctx; 80 | }; 81 | 82 | typedef struct 83 | { 84 | char name[MAX_LEN]; 85 | char id[MAX_LEN]; 86 | char cu[MAX_LEN]; 87 | char mf[MAX_LEN]; 88 | char mdl[MAX_LEN]; 89 | char sw[MAX_LEN]; 90 | t_ha_entity entities[MAX_ENTITIES]; 91 | int entitiy_count; 92 | } t_ha_info; 93 | 94 | void ha_setup(); 95 | void ha_connected(); 96 | bool ha_loop(); 97 | void ha_transmit_all(); 98 | void ha_publish(); 99 | void ha_add(t_ha_entity *entity); 100 | void ha_addmqtt(char *json_str, const char *name, const char *value, t_ha_entity *entity, bool last); 101 | void ha_received(char *topic, const char *payload); 102 | void ha_transmit(const t_ha_entity *entity, const char *value); 103 | void ha_transmit_topic(const char *stat_t, const char *value); 104 | int ha_parse_index(const char *options, const char *message); 105 | void ha_get_index(const char *options, int index, char *text); 106 | -------------------------------------------------------------------------------- /src/Config.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "Config.h" 6 | 7 | t_cfg current_config; 8 | bool config_valid = false; 9 | 10 | void cfg_save() 11 | { 12 | File file = SPIFFS.open("/config.dat", "w"); 13 | if (!file || file.isDirectory()) 14 | { 15 | return; 16 | } 17 | 18 | if (strlen(current_config.hostname) < 2) 19 | { 20 | strcpy(current_config.hostname, CONFIG_OTANAME); 21 | } 22 | 23 | file.write((uint8_t *)¤t_config, sizeof(current_config)); 24 | file.close(); 25 | } 26 | 27 | void cfg_reset() 28 | { 29 | memset(¤t_config, 0x00, sizeof(current_config)); 30 | 31 | current_config.magic = CONFIG_MAGIC; 32 | strcpy(current_config.hostname, CONFIG_OTANAME); 33 | strcpy(current_config.mqtt_server, ""); 34 | current_config.mqtt_port = 11883; 35 | strcpy(current_config.mqtt_user, ""); 36 | strcpy(current_config.mqtt_password, ""); 37 | strcpy(current_config.mqtt_client, CONFIG_OTANAME); 38 | current_config.mqtt_publish = 0; 39 | 40 | current_config.adc_corr = 1.0f; 41 | current_config.conv_usv_per_bq = 1.0f; 42 | current_config.voltage_target = 380; 43 | current_config.voltage_min = 100; 44 | current_config.voltage_max = 450; 45 | current_config.voltage_avg = 512; 46 | current_config.pwm_pid_i = 30; 47 | current_config.pwm_freq = 30000; 48 | current_config.pwm_freq_min = 22000; 49 | current_config.pwm_freq_max = 40000; 50 | current_config.pwm_value = 80; 51 | current_config.idle_color = 0; 52 | current_config.elevated_color = 0xFF0000; 53 | current_config.flash_color = 0xFFFFFF; 54 | current_config.elevated_level = 100; 55 | current_config.buzz_length = 20; 56 | current_config.buzz_freq = 1000; 57 | current_config.verbose = 7; 58 | 59 | strcpy(current_config.wifi_ssid, "(not set)"); 60 | strcpy(current_config.wifi_password, "(not set)"); 61 | } 62 | 63 | void cfg_read() 64 | { 65 | File file = SPIFFS.open("/config.dat", "r"); 66 | 67 | config_valid = false; 68 | 69 | if (!file || file.isDirectory()) 70 | { 71 | cfg_reset(); 72 | } 73 | else 74 | { 75 | file.read((uint8_t *)¤t_config, sizeof(current_config)); 76 | file.close(); 77 | 78 | if (current_config.magic != CONFIG_MAGIC) 79 | { 80 | /* on a minor version change, just keep wifi settings and hostname */ 81 | if ((current_config.magic & ~0xF) == (CONFIG_MAGIC & ~0xF)) 82 | { 83 | char hostname[32]; 84 | char wifi_ssid[32]; 85 | char wifi_password[32]; 86 | 87 | strcpy(hostname, current_config.hostname); 88 | strcpy(wifi_ssid, current_config.wifi_ssid); 89 | strcpy(wifi_password, current_config.wifi_password); 90 | 91 | cfg_reset(); 92 | 93 | strcpy(current_config.hostname, hostname); 94 | strcpy(current_config.wifi_ssid, wifi_ssid); 95 | strcpy(current_config.wifi_password, wifi_password); 96 | config_valid = true; 97 | } 98 | else 99 | { 100 | cfg_reset(); 101 | } 102 | return; 103 | } 104 | config_valid = true; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /include/GxEPD2_selection_check.h: -------------------------------------------------------------------------------- 1 | // Display Library example for SPI e-paper panels from Dalian Good Display and boards from Waveshare. 2 | // Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! 3 | // 4 | // Display Library based on Demo Example from Good Display: http://www.e-paper-display.com/download_list/downloadcategoryid=34&isMode=false.html 5 | // 6 | // Author: Jean-Marc Zingg 7 | // 8 | // Version: see library.properties 9 | // 10 | // Library: https://github.com/ZinggJM/GxEPD2 11 | 12 | // Supporting Arduino Forum Topics: 13 | // Waveshare e-paper displays with SPI: http://forum.arduino.cc/index.php?topic=487007.0 14 | // Good Display ePaper for Arduino: https://forum.arduino.cc/index.php?topic=436411.0 15 | 16 | #define GxEPD2_102_IS_BW true 17 | #define GxEPD2_154_IS_BW true 18 | #define GxEPD2_154_D67_IS_BW true 19 | #define GxEPD2_154_T8_IS_BW true 20 | #define GxEPD2_154_M09_IS_BW true 21 | #define GxEPD2_154_M10_IS_BW true 22 | #define GxEPD2_213_IS_BW true 23 | #define GxEPD2_213_B72_IS_BW true 24 | #define GxEPD2_213_B73_IS_BW true 25 | #define GxEPD2_213_B74_IS_BW true 26 | #define GxEPD2_213_flex_IS_BW true 27 | #define GxEPD2_213_M21_IS_BW true 28 | #define GxEPD2_213_T5D_IS_BW true 29 | #define GxEPD2_290_IS_BW true 30 | #define GxEPD2_290_T5_IS_BW true 31 | #define GxEPD2_290_T5D_IS_BW true 32 | #define GxEPD2_290_I6FD_IS_BW true 33 | #define GxEPD2_290_T94_IS_BW true 34 | #define GxEPD2_290_T94_V2_IS_BW true 35 | #define GxEPD2_290_M06_IS_BW true 36 | #define GxEPD2_260_IS_BW true 37 | #define GxEPD2_260_M01_IS_BW true 38 | #define GxEPD2_270_IS_BW true 39 | #define GxEPD2_371_IS_BW true 40 | #define GxEPD2_370_TC1_IS_BW true 41 | #define GxEPD2_420_IS_BW true 42 | #define GxEPD2_420_M01_IS_BW true 43 | #define GxEPD2_583_IS_BW true 44 | #define GxEPD2_583_T8_IS_BW true 45 | #define GxEPD2_750_IS_BW true 46 | #define GxEPD2_750_T7_IS_BW true 47 | #define GxEPD2_1160_T91_IS_BW true 48 | #define GxEPD2_1248_IS_BW true 49 | #define GxEPD2_it60_IS_BW true 50 | #define GxEPD2_it60_1448x1072_IS_BW true 51 | #define GxEPD2_it78_1872x1404_IS_BW true 52 | // 3-color e-papers 53 | #define GxEPD2_154c_IS_3C true 54 | #define GxEPD2_154_Z90c_IS_3C true 55 | #define GxEPD2_213c_IS_3C true 56 | #define GxEPD2_213_Z19c_IS_3C true 57 | #define GxEPD2_213_Z98c_IS_3C true 58 | #define GxEPD2_290c_IS_3C true 59 | #define GxEPD2_290_Z13c_IS_3C true 60 | #define GxEPD2_290_C90c_IS_3C true 61 | #define GxEPD2_270c_IS_3C true 62 | #define GxEPD2_420c_IS_3C true 63 | #define GxEPD2_420c_Z21_IS_3C true 64 | #define GxEPD2_583c_IS_3C true 65 | #define GxEPD2_750c_IS_3C true 66 | #define GxEPD2_750c_Z08_IS_3C true 67 | #define GxEPD2_750c_Z90_IS_3C true 68 | // 7-color e-paper 69 | #define GxEPD2_565c_IS_7C true 70 | 71 | #if defined(GxEPD2_DISPLAY_CLASS) && defined(GxEPD2_DRIVER_CLASS) 72 | #define IS_GxEPD2_DRIVER(c, x) (c##x) 73 | #define IS_GxEPD2_DRIVER_BW(x) IS_GxEPD2_DRIVER(x, _IS_BW) 74 | #define IS_GxEPD2_DRIVER_3C(x) IS_GxEPD2_DRIVER(x, _IS_3C) 75 | #define IS_GxEPD2_DRIVER_7C(x) IS_GxEPD2_DRIVER(x, _IS_7C) 76 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_3C(GxEPD2_DRIVER_CLASS) 77 | #error "GxEPD2_BW used with 3-color driver class" 78 | #endif 79 | #if IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_BW(GxEPD2_DRIVER_CLASS) 80 | #error "GxEPD2_3C used with b/w driver class" 81 | #endif 82 | #if !IS_GxEPD2_DRIVER_BW(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_DRIVER_3C(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS) 83 | #error "neither BW nor 3C nor 7C kind defined for driver class (error in GxEPD2_selection_check.h)" 84 | #endif 85 | 86 | #endif -------------------------------------------------------------------------------- /src/Environment.ino: -------------------------------------------------------------------------------- 1 | #include "Config.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Adafruit_CCS811.h" 9 | 10 | Adafruit_BME280 bme; 11 | Adafruit_CCS811 ccs; 12 | 13 | #define BME_ALTITUDE 395 14 | #define SEALEVELPRESSURE_HPA (1013.25) 15 | 16 | float esp32_hall = 0; 17 | 18 | float bme280_temperature = 0; 19 | float bme280_humidity = 0; 20 | float bme280_pressure = 0; 21 | 22 | bool bme280_detected = false; 23 | bool ccs811_detected = false; 24 | 25 | uint16_t ccs811_co2; 26 | uint16_t ccs811_tvoc; 27 | 28 | void env_setup() 29 | { 30 | Serial.println(F("[BME280] Searching")); 31 | 32 | Wire.begin(26, 25); 33 | bool status = bme.begin(0x76); 34 | if (status) 35 | { 36 | bme280_detected = true; 37 | Serial.println(F("[BME280] Detected successfully")); 38 | 39 | bme.setSampling(Adafruit_BME280::MODE_FORCED, Adafruit_BME280::SAMPLING_X1, Adafruit_BME280::SAMPLING_X1, Adafruit_BME280::SAMPLING_X1, Adafruit_BME280::FILTER_OFF); 40 | } 41 | else 42 | { 43 | Serial.println("[BME280] Could not find a valid sensor"); 44 | } 45 | 46 | Serial.println(F("[CCS811] Searching...")); 47 | ccs.begin(0x5B); 48 | if(ccs.available()) 49 | { 50 | ccs811_detected = true; 51 | Serial.println(F("[CCS811] Detected successfully")); 52 | 53 | float temp = ccs.calculateTemperature(); 54 | ccs.setTempOffset(temp - 25.0); 55 | ccs.setDriveMode(CCS811_DRIVE_MODE_10SEC); 56 | ccs.disableInterrupt(); 57 | } 58 | else 59 | { 60 | Serial.println("[CCS811] Could not find a valid sensor"); 61 | } 62 | 63 | Serial.println(); 64 | } 65 | 66 | bool env_loop() 67 | { 68 | uint32_t curTime = millis(); 69 | static int nextTime = 0; 70 | 71 | if (curTime >= nextTime) 72 | { 73 | esp32_hall = hallRead(); 74 | 75 | if(bme280_detected) 76 | { 77 | if(bme.takeForcedMeasurement()) 78 | { 79 | float temp = bme.readTemperature(); 80 | float humd = bme.readHumidity(); 81 | float pres = bme.readPressure() / 100.0F; 82 | float pres_corr = pres / pow(1 - BME_ALTITUDE/44330.0, 5.255); 83 | 84 | bme280_temperature = temp; 85 | bme280_pressure = pres_corr; 86 | bme280_humidity = humd; 87 | 88 | if(current_config.verbose & 2) 89 | { 90 | Serial.print("Temperature = "); 91 | Serial.print(bme280_temperature); 92 | Serial.println(" °C"); 93 | 94 | Serial.print("Pressure = "); 95 | Serial.print(bme280_pressure); 96 | Serial.println(" hPa"); 97 | 98 | Serial.print("Humidity = "); 99 | Serial.print(bme280_humidity); 100 | Serial.println(" %"); 101 | 102 | Serial.println(); 103 | } 104 | } 105 | else 106 | { 107 | Serial.println("[BME280] Failed to read data"); 108 | } 109 | } 110 | 111 | ccs.setEnvironmentalData(bme280_humidity, bme280_temperature); 112 | 113 | if(ccs811_detected && ccs.available()) 114 | { 115 | if(!ccs.readData()) 116 | { 117 | ccs811_co2 = ccs.geteCO2(); 118 | ccs811_tvoc = ccs.getTVOC(); 119 | 120 | if(current_config.verbose & 2) 121 | { 122 | Serial.print("CO2: "); 123 | Serial.print(ccs811_co2); 124 | Serial.print("ppm, TVOC: "); 125 | Serial.println(ccs811_tvoc); 126 | } 127 | } 128 | else 129 | { 130 | Serial.println("[CCS811] Failed to read data"); 131 | } 132 | } 133 | 134 | nextTime = curTime + 60000; 135 | return true; 136 | } 137 | 138 | return false; 139 | } 140 | -------------------------------------------------------------------------------- /src/Detector.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "Config.h" 4 | 5 | #define DET_GPIO 18 6 | 7 | #define DET_FADE 8 8 | #define DET_LAST_EVENT_COUNT 10 9 | 10 | uint32_t det_counts = 0; 11 | uint32_t det_counts_last = 0; 12 | uint32_t last_detects = 0; 13 | 14 | uint32_t led_det_r = 0; 15 | uint32_t led_det_g = 0; 16 | uint32_t led_det_b = 0; 17 | 18 | uint32_t led_det_r_dest = 0; 19 | uint32_t led_det_g_dest = 16; 20 | uint32_t led_det_b_dest = 0; 21 | 22 | uint32_t levels_elevated = 0; 23 | 24 | uint32_t det_last_events[DET_LAST_EVENT_COUNT]; 25 | uint32_t det_last_event_count = 0; 26 | float det_last_events_avg = 0; 27 | 28 | 29 | /* https://github.com/radhoo/uradmonitor_kit1/blob/master/code/geiger/detectors.cpp 30 | case GEIGER_TUBE_SBM20: return 0.006315; // CPM 19 31 | case GEIGER_TUBE_SI29BG: return 0.010000; // CPM 12 32 | case GEIGER_TUBE_SBM19: return 0.001500; // CPM 80 33 | case GEIGER_TUBE_LND712: return 0.005940; // CPM 20.20 34 | case GEIGER_TUBE_SBM20M: return 0.013333; // CPM 9 35 | case GEIGER_TUBE_SI22G: return 0.001714; // CPM 70 36 | case GEIGER_TUBE_STS5: return 0.006666; // CPM 18 37 | case GEIGER_TUBE_SI3BG: return 0.631578; // CPM 0.19 38 | case GEIGER_TUBE_SBM21: return 0.048000; // CPM 2.5 39 | case GEIGER_TUBE_SBT9: return 0.010900; // CPM 11 40 | case GEIGER_TUBE_SI1G: return 0.006000; // CPM 20 41 | case GEIGER_TUBE_SI8B: return 0.001108; // 42 | case GEIGER_TUBE_SBT10A: return 0.001105; // 43 | */ 44 | 45 | void IRAM_ATTR det_isr() 46 | { 47 | uint32_t current_millis = millis(); 48 | 49 | det_counts++; 50 | 51 | det_last_events[det_last_event_count++] = current_millis; 52 | det_last_event_count %= DET_LAST_EVENT_COUNT; 53 | } 54 | 55 | void det_setup() 56 | { 57 | det_counts = 0; 58 | last_detects = 0; 59 | pinMode(DET_GPIO, INPUT); 60 | attachInterrupt(DET_GPIO, det_isr, FALLING); 61 | } 62 | 63 | bool det_loop() 64 | { 65 | bool detected = last_detects != det_counts; 66 | bool hasWork = true; 67 | uint32_t current_millis = millis(); 68 | 69 | /* averaging method: measure time distance between n ticks and average CPM from that */ 70 | uint32_t time_min = 0xFFFFFFFF; 71 | for(int pos = 0; pos < DET_LAST_EVENT_COUNT; pos++) 72 | { 73 | if(det_last_events[pos]) 74 | { 75 | time_min = min(time_min, det_last_events[pos]); 76 | } 77 | } 78 | 79 | if(time_min != 0xFFFFFFFF) 80 | { 81 | uint32_t delta = current_millis - time_min; 82 | float det_this = 60.0f / ((delta) / DET_LAST_EVENT_COUNT / 1000.0f); 83 | det_last_events_avg = (65 * det_last_events_avg + det_this) / 66; 84 | } 85 | 86 | if(isinf(det_last_events_avg) || isnan(det_last_events_avg)) 87 | { 88 | det_last_events_avg = 0; 89 | } 90 | 91 | /* when averaging result is elevated, set warning level for 10s */ 92 | if(det_last_events_avg > current_config.elevated_level) 93 | { 94 | levels_elevated = current_millis + 10000; 95 | } 96 | 97 | if(levels_elevated > current_millis) 98 | { 99 | led_det_r_dest = (current_config.elevated_color >> 16) & 0xFF; 100 | led_det_g_dest = (current_config.elevated_color >> 8) & 0xFF; 101 | led_det_b_dest = (current_config.elevated_color >> 0) & 0xFF; 102 | } 103 | else 104 | { 105 | led_det_r_dest = (current_config.idle_color >> 16) & 0xFF; 106 | led_det_g_dest = (current_config.idle_color >> 8) & 0xFF; 107 | led_det_b_dest = (current_config.idle_color >> 0) & 0xFF; 108 | } 109 | 110 | led_det_r = ((DET_FADE - 1) * led_det_r + led_det_r_dest) / DET_FADE; 111 | led_det_g = ((DET_FADE - 1) * led_det_g + led_det_g_dest) / DET_FADE; 112 | led_det_b = ((DET_FADE - 1) * led_det_b + led_det_b_dest) / DET_FADE; 113 | 114 | if (detected) 115 | { 116 | last_detects = det_counts; 117 | if(current_config.verbose & 4) 118 | { 119 | led_det_r = (current_config.flash_color >> 16) & 0xFF; 120 | led_det_g = (current_config.flash_color >> 8) & 0xFF; 121 | led_det_b = (current_config.flash_color >> 0) & 0xFF; 122 | } 123 | } 124 | 125 | led_set_adv(2, led_det_r, led_det_g, led_det_b, false); 126 | led_set_adv(3, led_det_r, led_det_g, led_det_b, false); 127 | led_set_adv(4, led_det_r, led_det_g, led_det_b, false); 128 | led_set_adv(5, led_det_r, led_det_g, led_det_b, true); 129 | 130 | if (detected) 131 | { 132 | if(current_config.verbose & 2) 133 | { 134 | buz_tick(); 135 | } 136 | if(current_config.verbose & 1) 137 | { 138 | Serial.printf("Detected: %d\n", det_counts); 139 | } 140 | } 141 | 142 | if((led_det_r_dest == led_det_r) && (led_det_g_dest == led_det_g) && (led_det_b_dest == led_det_b)) 143 | { 144 | hasWork = false; 145 | } 146 | 147 | return hasWork; 148 | } 149 | 150 | uint32_t det_fetch() 151 | { 152 | det_counts_last = det_counts; 153 | 154 | det_counts = 0; 155 | 156 | return det_counts_last; 157 | } 158 | -------------------------------------------------------------------------------- /src/Geiger.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Config.h" 10 | #include "HA.h" 11 | 12 | uint32_t led_r = 0; 13 | uint32_t led_g = 0; 14 | uint32_t led_b = 0; 15 | uint32_t loopCount = 0; 16 | 17 | float main_duration_avg = 0; 18 | float main_duration = 0; 19 | float main_duration_max = 0; 20 | float main_duration_min = 1000000; 21 | float main_cycletime_avg = 0; 22 | float main_cycletime = 0; 23 | float main_cycletime_max = 0; 24 | float main_cycletime_min = 1000000; 25 | 26 | extern bool config_valid; 27 | 28 | 29 | void setup() 30 | { 31 | Serial.begin(115200); 32 | Serial.printf("\n\n\n"); 33 | 34 | Serial.printf("[i] SDK: '%s'\n", ESP.getSdkVersion()); 35 | Serial.printf("[i] CPU Speed: %d MHz\n", ESP.getCpuFreqMHz()); 36 | Serial.printf("[i] Chip Id: %06llX\n", ESP.getEfuseMac()); 37 | Serial.printf("[i] Flash Mode: %08X\n", ESP.getFlashChipMode()); 38 | Serial.printf("[i] Flash Size: %08X\n", ESP.getFlashChipSize()); 39 | Serial.printf("[i] Flash Speed: %d MHz\n", ESP.getFlashChipSpeed() / 1000000); 40 | Serial.printf("[i] Heap %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize()); 41 | Serial.printf("[i] SPIRam %d/%d\n", ESP.getFreePsram(), ESP.getPsramSize()); 42 | Serial.printf("\n"); 43 | Serial.printf("[i] Starting\n"); 44 | 45 | Serial.printf("[i] Setup LEDs\n"); 46 | led_setup(); 47 | Serial.printf("[i] Setup EPD\n"); 48 | epd_setup(); 49 | Serial.printf("[i] Setup SPIFFS\n"); 50 | if (!SPIFFS.begin(true)) 51 | { 52 | Serial.println("[E] SPIFFS Mount Failed"); 53 | } 54 | cfg_read(); 55 | Serial.printf("[i] Setup ADC\n"); 56 | adc_setup(); 57 | Serial.printf("[i] Setup Buzzer\n"); 58 | buz_setup(); 59 | Serial.printf("[i] Setup RTTTL player\n"); 60 | rtttl_setup(); 61 | Serial.printf("[i] Setup PWM\n"); 62 | pwm_setup(); 63 | Serial.printf("[i] Setup Detector\n"); 64 | det_setup(); 65 | Serial.printf("[i] Setup WiFi\n"); 66 | wifi_setup(); 67 | Serial.printf("[i] Setup Webserver\n"); 68 | www_setup(); 69 | Serial.printf("[i] Setup Time\n"); 70 | time_setup(); 71 | Serial.printf("[i] Setup MQTT\n"); 72 | mqtt_setup(); 73 | Serial.printf("[i] Setup BME280/CCS811\n"); 74 | env_setup(); 75 | 76 | Serial.println("Setup done"); 77 | } 78 | 79 | 80 | void loop() 81 | { 82 | static uint64_t lastStart = 0; 83 | bool hasWork = false; 84 | 85 | uint64_t microsEntry = micros(); 86 | 87 | if(lastStart > 0) 88 | { 89 | main_cycletime = microsEntry - lastStart; 90 | main_cycletime_avg = (15 * main_cycletime_avg + main_cycletime) / 16.0f; 91 | if (main_cycletime < main_cycletime_min) 92 | { 93 | main_cycletime_min = main_cycletime; 94 | } 95 | if (main_cycletime > main_cycletime_max) 96 | { 97 | main_cycletime_max = main_cycletime; 98 | } 99 | } 100 | lastStart = microsEntry; 101 | 102 | hasWork |= led_loop(); 103 | hasWork |= adc_loop(); 104 | hasWork |= env_loop(); 105 | hasWork |= buz_loop(); 106 | hasWork |= pwm_loop(); 107 | hasWork |= wifi_loop(); 108 | hasWork |= www_loop(); 109 | hasWork |= time_loop(); 110 | hasWork |= mqtt_loop(); 111 | hasWork |= ota_loop(); 112 | hasWork |= det_loop(); 113 | hasWork |= rtttl_loop(); 114 | 115 | 116 | uint64_t duration = micros() - microsEntry; 117 | 118 | main_duration = duration; 119 | main_duration_avg = (15 * main_duration_avg + duration) / 16.0f; 120 | 121 | if (main_duration < main_duration_min) 122 | { 123 | main_duration_min = main_duration; 124 | } 125 | if (main_duration > main_duration_max) 126 | { 127 | main_duration_max = main_duration; 128 | } 129 | 130 | bool voltage_ok = ((adc_get_voltage() >= current_config.voltage_min) && (adc_get_voltage() <= current_config.voltage_max)); 131 | 132 | if (((current_config.verbose & 8) && voltage_ok) || !config_valid) 133 | { 134 | led_r = (sin(loopCount / 50.0f) + 1) * 16; 135 | led_g = 0; 136 | led_b = (cos(loopCount / 50.0f) + 1) * 16; 137 | } 138 | else 139 | { 140 | led_r = voltage_ok ? 0 : 255; 141 | led_g = voltage_ok ? 16 : 0; 142 | led_b = 0; 143 | } 144 | 145 | if (!config_valid) 146 | { 147 | led_set_inhibit(false); 148 | for(int led = 0; led < 6; led++) 149 | { 150 | led_set_adv(led, led_r, led_g, led_b, led == 5); 151 | } 152 | led_set_inhibit(true); 153 | } 154 | else 155 | { 156 | led_set(0, led_r, led_g, led_b); 157 | } 158 | 159 | loopCount++; 160 | 161 | if(!hasWork) 162 | { 163 | delay(100); 164 | } 165 | else 166 | { 167 | delay(10); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /data/plot.html: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 24 | 25 | 26 |

Geiger plots

27 |
28 |
29 |
30 |
31 | 32 | 191 | 192 | -------------------------------------------------------------------------------- /src/WiFi.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | DNSServer dnsServer; 5 | 6 | bool connecting = false; 7 | bool wifi_captive = false; 8 | char wifi_error[64]; 9 | int wifi_rssi = 0; 10 | 11 | void wifi_setup() 12 | { 13 | Serial.printf("[WiFi] Connecting to '%s', password '%s'...\n", current_config.wifi_ssid, current_config.wifi_password); 14 | sprintf(wifi_error, ""); 15 | WiFi.begin(current_config.wifi_ssid, current_config.wifi_password); 16 | connecting = true; 17 | led_set(1, 8, 8, 0); 18 | } 19 | 20 | void wifi_off() 21 | { 22 | connecting = false; 23 | WiFi.disconnect(); 24 | WiFi.mode(WIFI_OFF); 25 | } 26 | 27 | void wifi_enter_captive() 28 | { 29 | wifi_off(); 30 | WiFi.softAP(CONFIG_SOFTAPNAME); 31 | dnsServer.start(53, "*", WiFi.softAPIP()); 32 | Serial.printf("[WiFi] Local IP: %s\n", WiFi.softAPIP().toString().c_str()); 33 | 34 | wifi_captive = true; 35 | 36 | /* reset captive idle timer */ 37 | www_activity(); 38 | } 39 | 40 | bool wifi_loop(void) 41 | { 42 | int status = WiFi.status(); 43 | uint32_t curTime = millis(); 44 | static uint32_t nextTime = 0; 45 | static uint32_t stateCounter = 0; 46 | 47 | if (wifi_captive) 48 | { 49 | dnsServer.processNextRequest(); 50 | led_set(1, 0, ((millis() % 250) > 125) ? 0 : 255, 0); 51 | 52 | /* captive mode, but noone cares */ 53 | if (!www_is_captive_active()) 54 | { 55 | Serial.printf("[WiFi] Timeout in captive, trying known networks again\n"); 56 | sprintf(wifi_error, "Timeout in captive, trying known networks again"); 57 | dnsServer.stop(); 58 | wifi_off(); 59 | wifi_captive = false; 60 | stateCounter = 0; 61 | sprintf(wifi_error, ""); 62 | } 63 | return true; 64 | } 65 | 66 | if (nextTime > curTime) 67 | { 68 | return false; 69 | } 70 | 71 | /* standard refresh time */ 72 | nextTime = curTime + 500; 73 | 74 | /* when stuck at a state, disconnect */ 75 | if (++stateCounter > 20) 76 | { 77 | Serial.printf("[WiFi] Timeout connecting\n"); 78 | sprintf(wifi_error, "Timeout - incorrect password?"); 79 | wifi_off(); 80 | } 81 | 82 | if (strcmp(wifi_error, "")) 83 | { 84 | Serial.printf("[WiFi] Entering captive mode. Reason: '%s'\n", wifi_error); 85 | 86 | wifi_enter_captive(); 87 | 88 | stateCounter = 0; 89 | return false; 90 | } 91 | 92 | switch (status) 93 | { 94 | case WL_CONNECTED: 95 | if (connecting) 96 | { 97 | led_set(1, 0, 4, 0); 98 | connecting = false; 99 | Serial.print("[WiFi] Connected, IP address: "); 100 | Serial.println(WiFi.localIP()); 101 | stateCounter = 0; 102 | sprintf(wifi_error, ""); 103 | } 104 | else 105 | { 106 | static int last_rssi = -1; 107 | wifi_rssi = WiFi.RSSI(); 108 | 109 | if (last_rssi != wifi_rssi) 110 | { 111 | float maxRssi = -70; 112 | float minRssi = -90; 113 | float strRatio = (wifi_rssi - minRssi) / (maxRssi - minRssi); 114 | float strength = min(1, max(0, strRatio)); 115 | float brightness = 0.05f; 116 | int r = brightness * 255.0f * (1.0f - strength); 117 | int g = brightness * 255.0f * strength; 118 | 119 | led_set(1, r, g, 0); 120 | 121 | if(current_config.verbose & 1) 122 | { 123 | Serial.printf("[WiFi] RSSI %d, strength: %1.2f, r: %d, g: %d\n", wifi_rssi, strength, r, g); 124 | } 125 | 126 | last_rssi = wifi_rssi; 127 | } 128 | 129 | /* happy with this state, reset counter */ 130 | stateCounter = 0; 131 | } 132 | break; 133 | 134 | case WL_CONNECTION_LOST: 135 | Serial.printf("[WiFi] Connection lost\n"); 136 | sprintf(wifi_error, "Network found, but connection lost"); 137 | led_set(1, 32, 8, 0); 138 | wifi_off(); 139 | break; 140 | 141 | case WL_CONNECT_FAILED: 142 | Serial.printf("[WiFi] Connection failed\n"); 143 | sprintf(wifi_error, "Network found, but connection failed"); 144 | wifi_off(); 145 | break; 146 | 147 | case WL_NO_SSID_AVAIL: 148 | Serial.printf("[WiFi] No SSID with that name\n"); 149 | sprintf(wifi_error, "Network not found"); 150 | wifi_off(); 151 | break; 152 | 153 | case WL_SCAN_COMPLETED: 154 | Serial.printf("[WiFi] Scan completed\n"); 155 | wifi_off(); 156 | break; 157 | 158 | case WL_DISCONNECTED: 159 | if (!connecting) 160 | { 161 | Serial.printf("[WiFi] Disconnected\n"); 162 | led_set(1, 255, 0, 255); 163 | wifi_off(); 164 | } 165 | break; 166 | 167 | case WL_IDLE_STATUS: 168 | if (!connecting) 169 | { 170 | connecting = true; 171 | Serial.printf("[WiFi] Idle, connect to '%s'\n", current_config.wifi_ssid); 172 | WiFi.mode(WIFI_STA); 173 | WiFi.begin(current_config.wifi_ssid, current_config.wifi_password); 174 | } 175 | else 176 | { 177 | Serial.printf("[WiFi] Idle, connecting...\n"); 178 | } 179 | break; 180 | 181 | case WL_NO_SHIELD: 182 | if (!connecting) 183 | { 184 | connecting = true; 185 | Serial.printf("[WiFi] Disabled (%d), connecting to '%s'\n", status, current_config.wifi_ssid); 186 | WiFi.mode(WIFI_STA); 187 | WiFi.begin(current_config.wifi_ssid, current_config.wifi_password); 188 | } 189 | break; 190 | 191 | default: 192 | Serial.printf("[WiFi] unknown (%d), disable\n", status); 193 | wifi_off(); 194 | break; 195 | } 196 | 197 | return false; 198 | } 199 | -------------------------------------------------------------------------------- /src/PWM.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "Config.h" 3 | #include "Macros.h" 4 | 5 | #define PWM_LEDC 1 6 | #define PWM_GPIO 27 7 | 8 | #define PWM_BITS 9 9 | #define PWM_PCT(x) ((uint32_t) ((100.0f-x) * ((1UL << (PWM_BITS)) - 1) / 100.0f)) 10 | 11 | 12 | float pwm_value = 0; 13 | float pwm_deviation = 0; 14 | uint32_t pwm_freq = 0; 15 | uint32_t pwm_test_active = 0; 16 | long pwm_next_check = 0; 17 | long pwm_confirm_start = 0; 18 | bool pwm_confirmed = false; 19 | 20 | 21 | void pwm_setup() 22 | { 23 | digitalWrite(PWM_GPIO, LOW); 24 | pinMode(PWM_GPIO, OUTPUT); 25 | 26 | Serial.printf("[i] Frequency %d Hz\n", current_config.pwm_freq); 27 | Serial.printf("[i] PWM resolution %d Bits\n", PWM_BITS); 28 | Serial.printf("[i] Startup freq %d Hz\n", current_config.pwm_freq); 29 | Serial.printf("[i] Startup duty %2.2f %%\n", current_config.pwm_value); 30 | pwm_value = current_config.pwm_value; 31 | pwm_freq = current_config.pwm_freq; 32 | 33 | ledcAttachPin(PWM_GPIO, PWM_LEDC); 34 | ledcSetup(PWM_LEDC, current_config.pwm_freq, PWM_BITS); 35 | ledcWrite(PWM_LEDC, PWM_PCT(pwm_value)); 36 | 37 | pwm_next_check = millis() + 500; 38 | 39 | pwm_confirmed = false; 40 | pwm_confirm_start = millis(); 41 | 42 | adc_reset_voltage(); 43 | } 44 | 45 | void pwm_testmode(uint32_t state) 46 | { 47 | if(pwm_test_active && !state) 48 | { 49 | pwm_setup(); 50 | } 51 | 52 | pwm_test_active = state; 53 | } 54 | 55 | void pwm_test(uint32_t frequency, float duty) 56 | { 57 | if(pwm_test_active) 58 | { 59 | pwm_value = duty; 60 | ledcSetup(PWM_LEDC, frequency, PWM_BITS); 61 | ledcWrite(PWM_LEDC, PWM_PCT(pwm_value)); 62 | adc_reset_voltage(); 63 | } 64 | } 65 | 66 | bool pwm_is_stable() 67 | { 68 | double deltaVoltage = current_config.voltage_target - adc_voltage_avg; 69 | 70 | return fabs(deltaVoltage) < 5; 71 | } 72 | 73 | bool pwm_loop() 74 | { 75 | /* safety check high prio */ 76 | if(adc_voltage_avg > current_config.voltage_max) 77 | { 78 | char msg[128]; 79 | 80 | pwm_value = 0; 81 | ledcWrite(PWM_LEDC, PWM_PCT(0)); 82 | 83 | sprintf(msg, "[PWM] Voltage %2.2f V avg (%2.2f V last sample) > %2.2f V, shutdown. PWM %d Hz", adc_voltage_avg, adc_voltage, current_config.voltage_max, pwm_freq); 84 | mqtt_publish_string((char *)"feeds/string/%s/error", msg); 85 | Serial.println(msg); 86 | 87 | return false; 88 | } 89 | 90 | if(pwm_test_active) 91 | { 92 | return false; 93 | } 94 | 95 | /* run every 500ms (also have a startup delay) */ 96 | if((millis() < pwm_next_check) || (pwm_value == 0)) 97 | { 98 | return false; 99 | } 100 | 101 | pwm_next_check = millis() + 500; 102 | 103 | if(current_config.verbose & 1) 104 | { 105 | Serial.printf("[PWM] Voltage %2.2f V (%2.2f V averaged)\n", adc_voltage, adc_voltage_avg); 106 | } 107 | 108 | /* safety check low prio, averaged */ 109 | if(adc_voltage_avg < current_config.voltage_min) 110 | { 111 | char msg[128]; 112 | 113 | pwm_value = 0; 114 | ledcWrite(PWM_LEDC, PWM_PCT(0)); 115 | 116 | sprintf(msg, "[PWM] Voltage %2.2f V avg (%2.2f V last sample) < %2.2f V, shutdown", adc_voltage_avg, adc_voltage, current_config.voltage_min); 117 | mqtt_publish_string((char *)"feeds/string/%s/error", msg); 118 | Serial.println(msg); 119 | } 120 | 121 | /* emergency disable active? */ 122 | if(pwm_value == 0) 123 | { 124 | ledcWrite(PWM_LEDC, PWM_PCT(0)); 125 | return false; 126 | } 127 | 128 | /* determine deviation */ 129 | double deltaVoltage = current_config.voltage_target - adc_voltage_avg; 130 | 131 | pwm_deviation *= 0.9f; 132 | pwm_deviation = (7 * pwm_deviation + 1 * (deltaVoltage*deltaVoltage)) / 8; 133 | 134 | /* calc I-term */ 135 | double deltaFreq = -deltaVoltage * current_config.pwm_pid_i; 136 | 137 | /* saturate I-term */ 138 | coerce(deltaFreq, -1000, 1000); 139 | 140 | /* integrate */ 141 | pwm_freq += deltaFreq; 142 | 143 | /* saturate PWM frequency */ 144 | coerce(pwm_freq, current_config.pwm_freq_min, current_config.pwm_freq_max); 145 | 146 | /* write PWM frequency */ 147 | ledcSetup(PWM_LEDC, pwm_freq, PWM_BITS); 148 | 149 | if(current_config.verbose & 1) 150 | { 151 | Serial.printf("[PWM] %2.2f V vs. %2.2f, PWM => %d Hz, deviation %1.2f\n", adc_voltage_avg, current_config.voltage_target, pwm_freq, pwm_deviation); 152 | } 153 | 154 | /* startup phase, check for stabilization */ 155 | if(!pwm_confirmed) 156 | { 157 | long expired = millis() - pwm_confirm_start; 158 | 159 | led_set_inhibit(false); 160 | 161 | for(int pos = 0; pos < 6; pos++) 162 | { 163 | led_set_adv(pos, 0, 0, 255, pos == 5); 164 | } 165 | 166 | /* wait at least 1 second */ 167 | if(expired >= 1000) 168 | { 169 | /* voltage within 10 V tolerance */ 170 | if(fabsf(deltaVoltage) < 10) 171 | { 172 | pwm_confirmed = true; 173 | 174 | if(!config_valid) 175 | { 176 | rtttl_play("Halloween:d=4, o=5, b=180:8d6, 8g, 8g, 8d6, 8g, 8g, 8d6, 8g, 8d#6, 8g, 8d6, 8g, 8g, 8d6, 8g, 8g, 8d6, 8g, 8d#6, 8g, 8c#6, 8f#, 8f#, 8c#6, 8f#, 8f#, 8c#6, 8f#, 8d6, 8f#, 8c#6, 8f#, 8f#, 8c#6, 8f#, 8f#, 8c#6, 8f#, 8d6, 8f#"); 177 | } 178 | else 179 | { 180 | buzz_beep(1000, 150); 181 | buzz_beep(1500, 150); 182 | buzz_beep(2500, 500); 183 | } 184 | Serial.printf("[PWM] Voltage came up after %d seconds\n", expired / 1000); 185 | } 186 | else 187 | { 188 | Serial.printf("[PWM] Voltage not up\n"); 189 | 190 | /* and up to 20 seconds if it gets stable */ 191 | if(expired >= 20000) 192 | { 193 | char msg[128]; 194 | 195 | for(int pos = 0; pos < 6; pos++) 196 | { 197 | led_set(pos, 255, 0, 0); 198 | } 199 | pwm_confirmed = true; 200 | 201 | buzz_beep(2500, 500); 202 | buzz_beep(1500, 500); 203 | buzz_beep(1000, 750); 204 | 205 | pwm_value = 0; 206 | ledcWrite(PWM_LEDC, PWM_PCT(0)); 207 | 208 | sprintf(msg, "[PWM] Voltage didn't come up properly after %d seconds (%2.2f V)", expired / 1000, adc_voltage_avg); 209 | mqtt_publish_string((char *)"feeds/string/%s/error", msg); 210 | Serial.println(msg); 211 | 212 | delay(500); 213 | } 214 | } 215 | } 216 | 217 | for(int pos = 0; pos < 6; pos++) 218 | { 219 | led_set_adv(pos, 0, 0, 0, pos == 5); 220 | } 221 | 222 | led_set_inhibit(!pwm_confirmed); 223 | } 224 | 225 | return !pwm_is_stable(); 226 | } 227 | -------------------------------------------------------------------------------- /src/Time.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "Time.h" 4 | 5 | enum statusType 6 | { 7 | Idle, 8 | Sent, 9 | Received, 10 | Pause 11 | }; 12 | 13 | IPAddress timeServerIP; // time.nist.gov NTP server address 14 | const char *ntpServerName = "time.nist.gov"; 15 | unsigned int localPort = 2390; // local port to listen for UDP packets 16 | const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message 17 | byte packetBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets 18 | WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP 19 | 20 | uint32_t retries = 0; 21 | unsigned long lastSent = 0; 22 | unsigned long timeReference = 0; 23 | bool time_valid = false; 24 | unsigned long secsSince1900 = 0; 25 | unsigned long setup_time_offset = 2; 26 | 27 | /* 2000-03-01 (mod 400 year, immediately after feb29 */ 28 | #define LEAPOCH (946684800LL + 86400 * (31 + 29)) 29 | #define DAYS_PER_400Y (365 * 400 + 97) 30 | #define DAYS_PER_100Y (365 * 100 + 24) 31 | #define DAYS_PER_4Y (365 * 4 + 1) 32 | 33 | statusType currentStatus = Idle; 34 | 35 | void time_setup() 36 | { 37 | Udp.begin(localPort); 38 | currentStatus = Idle; 39 | lastSent = 0; 40 | timeReference = 0; 41 | memset(packetBuffer, 0x00, sizeof(packetBuffer)); 42 | } 43 | 44 | const char *Time_getStateString() 45 | { 46 | static char retString[64]; 47 | const char *state = ""; 48 | 49 | switch (currentStatus) 50 | { 51 | default: 52 | state = "Unknown state"; 53 | break; 54 | 55 | case Idle: 56 | state = "Idle"; 57 | break; 58 | 59 | case Sent: 60 | state = "Sent"; 61 | break; 62 | 63 | case Received: 64 | state = "Received"; 65 | break; 66 | 67 | case Pause: 68 | state = "Pause"; 69 | break; 70 | } 71 | 72 | snprintf(retString, sizeof(retString), "%s, ref: %lu, last: %lu, retries: %u, millis(): %lu", state, timeReference, lastSent, retries, millis()); 73 | 74 | return retString; 75 | } 76 | 77 | bool time_loop() 78 | { 79 | if (WiFi.status() != WL_CONNECTED) 80 | { 81 | return false; 82 | } 83 | 84 | switch (currentStatus) 85 | { 86 | case Idle: 87 | if (!time_valid || millis() - lastSent > 1000 * 60 * 60) 88 | { 89 | Serial.println("[NTP] Sending request"); 90 | 91 | lastSent = millis(); 92 | currentStatus = Sent; 93 | WiFi.hostByName(ntpServerName, timeServerIP); 94 | sendNTPpacket(timeServerIP); // send an NTP packet to a time server 95 | } 96 | break; 97 | 98 | case Sent: 99 | if (millis() - lastSent > 1000 * 10) 100 | { 101 | Serial.println("[NTP] No reply, resend"); 102 | if (retries < 10) 103 | { 104 | retries++; 105 | currentStatus = Idle; 106 | } 107 | else 108 | { 109 | currentStatus = Pause; 110 | } 111 | } 112 | else if (Udp.parsePacket()) 113 | { 114 | timeReference = millis(); 115 | currentStatus = Received; 116 | Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer 117 | } 118 | break; 119 | 120 | case Received: 121 | { 122 | unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); 123 | unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); 124 | secsSince1900 = highWord << 16 | lowWord; 125 | 126 | printTime(); 127 | 128 | if (!time_valid) 129 | { 130 | time_valid = true; 131 | } 132 | 133 | retries = 0; 134 | currentStatus = Idle; 135 | break; 136 | } 137 | 138 | case Pause: 139 | if (millis() - lastSent > 1000 * 60 * 2) 140 | { 141 | currentStatus = Idle; 142 | } 143 | break; 144 | 145 | default: 146 | Serial.println("[NTP] Unknown state"); 147 | currentStatus = Idle; 148 | break; 149 | } 150 | 151 | return false; 152 | } 153 | 154 | void printTime() 155 | { 156 | struct tm tm; 157 | getTime(&tm); 158 | 159 | Serial.printf("[NTP] The time is: %02d:%02d:%02d\n", tm.tm_hour, tm.tm_min, tm.tm_sec); 160 | } 161 | 162 | int secs_to_tm(long long t, struct tm *tm) 163 | { 164 | long long days, secs; 165 | int remdays, remsecs, remyears; 166 | int qc_cycles, c_cycles, q_cycles; 167 | int years, months; 168 | int wday, yday, leap; 169 | static const char days_in_month[] = {31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29}; 170 | 171 | secs = t - LEAPOCH; 172 | days = secs / 86400; 173 | remsecs = secs % 86400; 174 | if (remsecs < 0) 175 | { 176 | remsecs += 86400; 177 | days--; 178 | } 179 | 180 | wday = (3 + days) % 7; 181 | if (wday < 0) 182 | wday += 7; 183 | 184 | qc_cycles = days / DAYS_PER_400Y; 185 | remdays = days % DAYS_PER_400Y; 186 | if (remdays < 0) 187 | { 188 | remdays += DAYS_PER_400Y; 189 | qc_cycles--; 190 | } 191 | 192 | c_cycles = remdays / DAYS_PER_100Y; 193 | if (c_cycles == 4) 194 | c_cycles--; 195 | remdays -= c_cycles * DAYS_PER_100Y; 196 | 197 | q_cycles = remdays / DAYS_PER_4Y; 198 | if (q_cycles == 25) 199 | q_cycles--; 200 | remdays -= q_cycles * DAYS_PER_4Y; 201 | 202 | remyears = remdays / 365; 203 | if (remyears == 4) 204 | remyears--; 205 | remdays -= remyears * 365; 206 | 207 | leap = !remyears && (q_cycles || !c_cycles); 208 | yday = remdays + 31 + 28 + leap; 209 | if (yday >= 365 + leap) 210 | yday -= 365 + leap; 211 | 212 | years = remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles; 213 | 214 | for (months = 0; days_in_month[months] <= remdays; months++) 215 | remdays -= days_in_month[months]; 216 | 217 | tm->tm_year = years + 100; 218 | tm->tm_mon = months + 2; 219 | if (tm->tm_mon >= 12) 220 | { 221 | tm->tm_mon -= 12; 222 | tm->tm_year++; 223 | } 224 | tm->tm_mday = remdays; 225 | tm->tm_wday = wday; 226 | tm->tm_yday = yday; 227 | 228 | tm->tm_hour = remsecs / 3600; 229 | tm->tm_min = remsecs / 60 % 60; 230 | tm->tm_sec = remsecs % 60; 231 | 232 | return 0; 233 | } 234 | 235 | void getTimeAdv(struct tm *tm, unsigned long offset) 236 | { 237 | unsigned long epoch = secsSince1900 - 2208988800UL; 238 | 239 | long secs = ((long)offset - (long)timeReference) / 1000; 240 | epoch += 60 * 60 * setup_time_offset; 241 | epoch += secs; 242 | 243 | secs_to_tm(epoch, tm); 244 | } 245 | 246 | void getTime(struct tm *tm) 247 | { 248 | getTimeAdv(tm, millis()); 249 | } 250 | 251 | void getStartupTime(struct tm *tm) 252 | { 253 | getTimeAdv(tm, 0); 254 | } 255 | 256 | // send an NTP request to the time server at the given address 257 | void sendNTPpacket(IPAddress &address) 258 | { 259 | memset(packetBuffer, 0, NTP_PACKET_SIZE); 260 | 261 | // Initialize values needed to form NTP request 262 | packetBuffer[0] = 0b11100011; // LI, Version, Mode 263 | packetBuffer[1] = 0; // Stratum, or type of clock 264 | packetBuffer[2] = 6; // Polling Interval 265 | packetBuffer[3] = 0xEC; // Peer Clock Precision 266 | // 8 bytes of zero for Root Delay & Root Dispersion 267 | packetBuffer[12] = 49; 268 | packetBuffer[13] = 0x4E; 269 | packetBuffer[14] = 49; 270 | packetBuffer[15] = 52; 271 | 272 | Udp.beginPacket(address, 123); // NTP requests are to port 123 273 | Udp.write(packetBuffer, NTP_PACKET_SIZE); 274 | Udp.endPacket(); 275 | } 276 | -------------------------------------------------------------------------------- /src/HA.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | t_ha_info ha_info; 11 | extern PubSubClient mqtt; 12 | 13 | void ha_addstrarray(char *json_str, const char *name, const char *value, bool last = false) 14 | { 15 | char tmp_buf[128]; 16 | 17 | if (value && strlen(value) > 0) 18 | { 19 | int pos = 0; 20 | char values_buf[128]; 21 | int out_pos = 0; 22 | 23 | values_buf[out_pos++] = '"'; 24 | 25 | bool done = false; 26 | while (!done && out_pos < sizeof(values_buf)) 27 | { 28 | switch (value[pos]) 29 | { 30 | case ';': 31 | values_buf[out_pos++] = '"'; 32 | if (value[pos + 1]) 33 | { 34 | values_buf[out_pos++] = ','; 35 | values_buf[out_pos++] = '"'; 36 | } 37 | break; 38 | 39 | case 0: 40 | values_buf[out_pos++] = '"'; 41 | done = true; 42 | break; 43 | 44 | default: 45 | values_buf[out_pos++] = value[pos]; 46 | break; 47 | } 48 | pos++; 49 | } 50 | values_buf[out_pos++] = '\000'; 51 | 52 | snprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": [%s]%c ", name, values_buf, (last ? ' ' : ',')); 53 | strcat(json_str, tmp_buf); 54 | } 55 | } 56 | 57 | void ha_addstr(char *json_str, const char *name, const char *value, bool last = false) 58 | { 59 | char tmp_buf[128]; 60 | 61 | if (value && strlen(value) > 0) 62 | { 63 | snprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%s\"%c ", name, value, (last ? ' ' : ',')); 64 | strcat(json_str, tmp_buf); 65 | } 66 | } 67 | 68 | void ha_addmqtt(char *json_str, const char *name, const char *value, t_ha_entity *entity, bool last = false) 69 | { 70 | char tmp_buf[128]; 71 | 72 | if (value && strlen(value) > 0) 73 | { 74 | char path_buffer[64]; 75 | 76 | if (entity && entity->alt_name) 77 | { 78 | sprintf(path_buffer, value, entity->alt_name); 79 | } 80 | else 81 | { 82 | sprintf(path_buffer, value, current_config.mqtt_client); 83 | } 84 | snprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%s\"%c ", name, path_buffer, (last ? ' ' : ',')); 85 | strcat(json_str, tmp_buf); 86 | } 87 | } 88 | 89 | void ha_addfloat(char *json_str, const char *name, float value, bool last = false) 90 | { 91 | char tmp_buf[64]; 92 | 93 | snprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%f\"%c ", name, value, (last ? ' ' : ',')); 94 | strcat(json_str, tmp_buf); 95 | } 96 | 97 | void ha_addint(char *json_str, const char *name, int value, bool last = false) 98 | { 99 | char tmp_buf[64]; 100 | 101 | snprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%d\"%c ", name, value, (last ? ' ' : ',')); 102 | strcat(json_str, tmp_buf); 103 | } 104 | 105 | void ha_publish() 106 | { 107 | char *json_str = (char *)malloc(1024); 108 | char mqtt_path[128]; 109 | char uniq_id[128]; 110 | 111 | Serial.printf("[HA] Publish\n"); 112 | 113 | sprintf(ha_info.cu, "http://%s/", WiFi.localIP().toString().c_str()); 114 | 115 | for (int pos = 0; pos < ha_info.entitiy_count; pos++) 116 | { 117 | const char *type = NULL; 118 | 119 | switch (ha_info.entities[pos].type) 120 | { 121 | case ha_sensor: 122 | Serial.printf("[HA] sensor\n"); 123 | type = "sensor"; 124 | break; 125 | case ha_text: 126 | Serial.printf("[HA] text\n"); 127 | type = "text"; 128 | break; 129 | case ha_number: 130 | Serial.printf("[HA] number\n"); 131 | type = "number"; 132 | break; 133 | case ha_button: 134 | Serial.printf("[HA] button\n"); 135 | type = "button"; 136 | break; 137 | case ha_binary_sensor: 138 | Serial.printf("[HA] binary_sensor\n"); 139 | type = "binary_sensor"; 140 | break; 141 | case ha_select: 142 | Serial.printf("[HA] select\n"); 143 | type = "select"; 144 | break; 145 | case ha_light: 146 | Serial.printf("[HA] light\n"); 147 | type = "light"; 148 | break; 149 | default: 150 | Serial.printf("[HA] last one\n"); 151 | break; 152 | } 153 | 154 | if (!type) 155 | { 156 | break; 157 | } 158 | 159 | sprintf(uniq_id, "%s_%s", ha_info.id, ha_info.entities[pos].id); 160 | 161 | Serial.printf("[HA] uniq_id %s\n", uniq_id); 162 | sprintf(mqtt_path, "homeassistant/%s/%s/%s/config", type, ha_info.id, ha_info.entities[pos].id); 163 | 164 | Serial.printf("[HA] mqtt_path %s\n", mqtt_path); 165 | 166 | strcpy(json_str, "{"); 167 | ha_addstr(json_str, "name", ha_info.entities[pos].name); 168 | ha_addstr(json_str, "uniq_id", uniq_id); 169 | ha_addstr(json_str, "dev_cla", ha_info.entities[pos].dev_class); 170 | ha_addstr(json_str, "stat_cla", ha_info.entities[pos].state_class); 171 | ha_addstr(json_str, "ic", ha_info.entities[pos].ic); 172 | ha_addstr(json_str, "mode", ha_info.entities[pos].mode); 173 | ha_addstr(json_str, "ent_cat", ha_info.entities[pos].ent_cat); 174 | ha_addmqtt(json_str, "cmd_t", ha_info.entities[pos].cmd_t, &ha_info.entities[pos]); 175 | ha_addmqtt(json_str, "stat_t", ha_info.entities[pos].stat_t, &ha_info.entities[pos]); 176 | ha_addmqtt(json_str, "rgbw_cmd_t", ha_info.entities[pos].rgbw_t, &ha_info.entities[pos]); 177 | ha_addmqtt(json_str, "rgb_cmd_t", ha_info.entities[pos].rgb_t, &ha_info.entities[pos]); 178 | ha_addmqtt(json_str, "fx_cmd_t", ha_info.entities[pos].fx_cmd_t, &ha_info.entities[pos]); 179 | ha_addmqtt(json_str, "fx_stat_t", ha_info.entities[pos].fx_stat_t, &ha_info.entities[pos]); 180 | ha_addstrarray(json_str, "fx_list", ha_info.entities[pos].fx_list); 181 | ha_addmqtt(json_str, "val_tpl", ha_info.entities[pos].val_tpl, &ha_info.entities[pos]); 182 | ha_addstrarray(json_str, "options", ha_info.entities[pos].options); 183 | ha_addstr(json_str, "unit_of_meas", ha_info.entities[pos].unit_of_meas); 184 | 185 | switch (ha_info.entities[pos].type) 186 | { 187 | case ha_number: 188 | ha_addint(json_str, "min", ha_info.entities[pos].min); 189 | ha_addint(json_str, "max", ha_info.entities[pos].max); 190 | break; 191 | default: 192 | break; 193 | } 194 | 195 | strcat(json_str, "\"dev\": {"); 196 | ha_addstr(json_str, "name", ha_info.name); 197 | ha_addstr(json_str, "ids", ha_info.id); 198 | ha_addstr(json_str, "cu", ha_info.cu); 199 | ha_addstr(json_str, "mf", ha_info.mf); 200 | ha_addstr(json_str, "mdl", ha_info.mdl); 201 | ha_addstr(json_str, "sw", ha_info.sw, true); 202 | strcat(json_str, "}}"); 203 | 204 | Serial.printf("[HA] topic '%s'\n", mqtt_path); 205 | Serial.printf("[HA] content '%s'\n", json_str); 206 | 207 | if (!mqtt.publish(mqtt_path, json_str)) 208 | { 209 | Serial.printf("[HA] publish failed\n"); 210 | } 211 | } 212 | 213 | Serial.printf("[HA] done\n"); 214 | free(json_str); 215 | } 216 | 217 | void ha_received(char *topic, const char *payload) 218 | { 219 | for (int pos = 0; pos < ha_info.entitiy_count; pos++) 220 | { 221 | char item_topic[128]; 222 | 223 | if (ha_info.entities[pos].cmd_t && ha_info.entities[pos].received) 224 | { 225 | sprintf(item_topic, ha_info.entities[pos].cmd_t, current_config.mqtt_client); 226 | if (!strcmp(topic, item_topic)) 227 | { 228 | ha_info.entities[pos].received(&ha_info.entities[pos], ha_info.entities[pos].received_ctx, payload); 229 | 230 | if (ha_info.entities[pos].transmit) 231 | { 232 | ha_info.entities[pos].transmit(&ha_info.entities[pos], ha_info.entities[pos].transmit_ctx); 233 | } 234 | } 235 | } 236 | 237 | if (ha_info.entities[pos].rgb_t && ha_info.entities[pos].rgb_received) 238 | { 239 | sprintf(item_topic, ha_info.entities[pos].rgb_t, current_config.mqtt_client); 240 | if (!strcmp(topic, item_topic)) 241 | { 242 | ha_info.entities[pos].rgb_received(&ha_info.entities[pos], ha_info.entities[pos].rgb_received_ctx, payload); 243 | 244 | if (ha_info.entities[pos].transmit) 245 | { 246 | ha_info.entities[pos].transmit(&ha_info.entities[pos], ha_info.entities[pos].transmit_ctx); 247 | } 248 | } 249 | } 250 | 251 | if (ha_info.entities[pos].fx_cmd_t && ha_info.entities[pos].fx_received) 252 | { 253 | sprintf(item_topic, ha_info.entities[pos].fx_cmd_t, current_config.mqtt_client); 254 | if (!strcmp(topic, item_topic)) 255 | { 256 | ha_info.entities[pos].fx_received(&ha_info.entities[pos], ha_info.entities[pos].fx_received_ctx, payload); 257 | 258 | if (ha_info.entities[pos].transmit) 259 | { 260 | ha_info.entities[pos].transmit(&ha_info.entities[pos], ha_info.entities[pos].transmit_ctx); 261 | } 262 | } 263 | } 264 | } 265 | } 266 | 267 | void ha_transmit(const t_ha_entity *entity, const char *value) 268 | { 269 | if (!entity) 270 | { 271 | return; 272 | } 273 | 274 | if (!entity->stat_t) 275 | { 276 | return; 277 | } 278 | char item_topic[128]; 279 | sprintf(item_topic, entity->stat_t, current_config.mqtt_client); 280 | 281 | if (!mqtt.publish(item_topic, value)) 282 | { 283 | Serial.printf("[HA] publish failed\n"); 284 | } 285 | } 286 | 287 | void ha_transmit_topic(const char *stat_t, const char *value) 288 | { 289 | if (!stat_t) 290 | { 291 | return; 292 | } 293 | 294 | char item_topic[128]; 295 | sprintf(item_topic, stat_t, current_config.mqtt_client); 296 | 297 | if (!mqtt.publish(item_topic, value)) 298 | { 299 | Serial.printf("[HA] publish failed\n"); 300 | } 301 | } 302 | 303 | void ha_transmit_all() 304 | { 305 | for (int pos = 0; pos < ha_info.entitiy_count; pos++) 306 | { 307 | if (ha_info.entities[pos].transmit) 308 | { 309 | ha_info.entities[pos].transmit(&ha_info.entities[pos], ha_info.entities[pos].transmit_ctx); 310 | } 311 | } 312 | } 313 | 314 | void ha_setup() 315 | { 316 | memset(&ha_info, 0x00, sizeof(ha_info)); 317 | 318 | sprintf(ha_info.name, "%s", current_config.mqtt_client); 319 | sprintf(ha_info.id, "%06llX", ESP.getEfuseMac()); 320 | sprintf(ha_info.cu, "http://%s/", WiFi.localIP().toString().c_str()); 321 | sprintf(ha_info.mf, "g3gg0.de"); 322 | sprintf(ha_info.mdl, ""); 323 | sprintf(ha_info.sw, "v1." xstr(PIO_SRC_REVNUM) " (" xstr(PIO_SRC_REV) ")"); 324 | ha_info.entitiy_count = 0; 325 | 326 | mqtt.setBufferSize(512); 327 | } 328 | 329 | void ha_connected() 330 | { 331 | for (int pos = 0; pos < ha_info.entitiy_count; pos++) 332 | { 333 | char item_topic[128]; 334 | if (ha_info.entities[pos].cmd_t && ha_info.entities[pos].received) 335 | { 336 | sprintf(item_topic, ha_info.entities[pos].cmd_t, current_config.mqtt_client); 337 | mqtt.subscribe(item_topic); 338 | } 339 | if (ha_info.entities[pos].rgb_t && ha_info.entities[pos].rgb_received) 340 | { 341 | sprintf(item_topic, ha_info.entities[pos].rgb_t, current_config.mqtt_client); 342 | mqtt.subscribe(item_topic); 343 | } 344 | if (ha_info.entities[pos].fx_cmd_t && ha_info.entities[pos].fx_received) 345 | { 346 | sprintf(item_topic, ha_info.entities[pos].fx_cmd_t, current_config.mqtt_client); 347 | mqtt.subscribe(item_topic); 348 | } 349 | } 350 | ha_publish(); 351 | ha_transmit_all(); 352 | } 353 | 354 | bool ha_loop() 355 | { 356 | uint32_t time = millis(); 357 | static uint32_t nextTime = 0; 358 | 359 | if (time >= nextTime) 360 | { 361 | ha_publish(); 362 | ha_transmit_all(); 363 | nextTime = time + 60000; 364 | } 365 | 366 | return false; 367 | } 368 | 369 | void ha_add(t_ha_entity *entity) 370 | { 371 | if (!entity) 372 | { 373 | return; 374 | } 375 | 376 | if (ha_info.entitiy_count >= MAX_ENTITIES) 377 | { 378 | return; 379 | } 380 | memcpy(&ha_info.entities[ha_info.entitiy_count++], entity, sizeof(t_ha_entity)); 381 | } 382 | 383 | int ha_parse_index(const char *options, const char *message) 384 | { 385 | if (!options) 386 | { 387 | return -1; 388 | } 389 | 390 | int pos = 0; 391 | char tmp_buf[128]; 392 | char *cur_elem = tmp_buf; 393 | 394 | strncpy(tmp_buf, options, sizeof(tmp_buf)); 395 | 396 | while (true) 397 | { 398 | char *next_elem = strchr(cur_elem, ';'); 399 | if (next_elem) 400 | { 401 | *next_elem = '\000'; 402 | } 403 | if (!strcmp(cur_elem, message)) 404 | { 405 | return pos; 406 | } 407 | 408 | if (!next_elem) 409 | { 410 | return -1; 411 | } 412 | 413 | cur_elem = next_elem + 1; 414 | pos++; 415 | } 416 | } 417 | 418 | void ha_get_index(const char *options, int index, char *text) 419 | { 420 | if (!options || !text) 421 | { 422 | return; 423 | } 424 | 425 | int pos = 0; 426 | char tmp_buf[128]; 427 | char *cur_elem = tmp_buf; 428 | 429 | strncpy(tmp_buf, options, sizeof(tmp_buf)); 430 | 431 | while (true) 432 | { 433 | char *next_elem = strchr(cur_elem, ';'); 434 | if (next_elem) 435 | { 436 | *next_elem = '\000'; 437 | } 438 | if (pos == index) 439 | { 440 | strcpy(text, cur_elem); 441 | return; 442 | } 443 | 444 | if (!next_elem) 445 | { 446 | return; 447 | } 448 | 449 | cur_elem = next_elem + 1; 450 | pos++; 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /src/MQTT.ino: -------------------------------------------------------------------------------- 1 | 2 | // #define TESTMODE 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "HA.h" 9 | 10 | WiFiClient client; 11 | PubSubClient mqtt(client); 12 | 13 | extern int wifi_rssi; 14 | extern float pwm_value; 15 | extern uint32_t pwm_freq; 16 | extern float pwm_deviation; 17 | extern float esp32_temperature; 18 | 19 | extern float main_duration_avg; 20 | extern float main_duration_max; 21 | extern float main_duration_min; 22 | extern float main_duration; 23 | 24 | extern float main_cycletime_avg; 25 | extern float main_cycletime_max; 26 | extern float main_cycletime_min; 27 | extern float main_cycletime; 28 | 29 | uint32_t ticks_total = 0; 30 | 31 | uint32_t mqtt_last_publish_time = 0; 32 | uint32_t mqtt_lastConnect = 0; 33 | uint32_t mqtt_retries = 0; 34 | bool mqtt_fail = false; 35 | 36 | char command_topic[64]; 37 | char response_topic[64]; 38 | 39 | void callback(char *topic, byte *payload, unsigned int length) 40 | { 41 | Serial.print("Message arrived ["); 42 | Serial.print(topic); 43 | Serial.print("] "); 44 | Serial.print("'"); 45 | for (int i = 0; i < length; i++) 46 | { 47 | Serial.print((char)payload[i]); 48 | } 49 | Serial.print("'"); 50 | Serial.println(); 51 | 52 | payload[length] = 0; 53 | 54 | ha_received(topic, (const char *)payload); 55 | 56 | if (!strcmp(topic, command_topic)) 57 | { 58 | char *command = (char *)payload; 59 | char buf[1024]; 60 | 61 | if (!strncmp(command, "http", 4)) 62 | { 63 | snprintf(buf, sizeof(buf) - 1, "updating from: '%s'", command); 64 | Serial.printf("%s\n", buf); 65 | 66 | mqtt.publish(response_topic, buf); 67 | ESPhttpUpdate.rebootOnUpdate(false); 68 | t_httpUpdate_return ret = ESPhttpUpdate.update(command); 69 | 70 | switch (ret) 71 | { 72 | case HTTP_UPDATE_FAILED: 73 | snprintf(buf, sizeof(buf) - 1, "HTTP_UPDATE_FAILED Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); 74 | mqtt.publish(response_topic, buf); 75 | Serial.printf("%s\n", buf); 76 | break; 77 | 78 | case HTTP_UPDATE_NO_UPDATES: 79 | snprintf(buf, sizeof(buf) - 1, "HTTP_UPDATE_NO_UPDATES"); 80 | mqtt.publish(response_topic, buf); 81 | Serial.printf("%s\n", buf); 82 | break; 83 | 84 | case HTTP_UPDATE_OK: 85 | snprintf(buf, sizeof(buf) - 1, "HTTP_UPDATE_OK"); 86 | mqtt.publish(response_topic, buf); 87 | Serial.printf("%s\n", buf); 88 | delay(500); 89 | ESP.restart(); 90 | break; 91 | 92 | default: 93 | snprintf(buf, sizeof(buf) - 1, "update failed"); 94 | mqtt.publish(response_topic, buf); 95 | Serial.printf("%s\n", buf); 96 | break; 97 | } 98 | } 99 | else 100 | { 101 | snprintf(buf, sizeof(buf) - 1, "unknown command: '%s'", command); 102 | mqtt.publish(response_topic, buf); 103 | Serial.printf("%s\n", buf); 104 | } 105 | } 106 | } 107 | 108 | void mqtt_ota_received(const t_ha_entity *entity, void *ctx, const char *message) 109 | { 110 | ota_setup(); 111 | } 112 | 113 | void mqtt_setup() 114 | { 115 | mqtt.setCallback(callback); 116 | 117 | ha_setup(); 118 | 119 | t_ha_entity entity; 120 | 121 | memset(&entity, 0x00, sizeof(entity)); 122 | entity.id = "ota"; 123 | entity.name = "Enable OTA"; 124 | entity.type = ha_button; 125 | entity.cmd_t = "command/%s/ota"; 126 | entity.received = &mqtt_ota_received; 127 | ha_add(&entity); 128 | 129 | memset(&entity, 0x00, sizeof(entity)); 130 | entity.id = "voltage"; 131 | entity.dev_class = "voltage"; 132 | entity.name = "Tube voltage"; 133 | entity.type = ha_sensor; 134 | entity.stat_t = "feeds/float/%s/voltage"; 135 | entity.unit_of_meas = "V"; 136 | ha_add(&entity); 137 | 138 | memset(&entity, 0x00, sizeof(entity)); 139 | entity.id = "pwm_freq"; 140 | entity.dev_class = "frequency"; 141 | entity.name = "PWM Frequency"; 142 | entity.type = ha_sensor; 143 | entity.stat_t = "feeds/float/%s/pwm_freq"; 144 | entity.unit_of_meas = "Hz"; 145 | ha_add(&entity); 146 | 147 | memset(&entity, 0x00, sizeof(entity)); 148 | entity.id = "pwm_value"; 149 | entity.name = "PWM duty cycle"; 150 | entity.type = ha_sensor; 151 | entity.stat_t = "feeds/float/%s/pwm_value"; 152 | entity.unit_of_meas = "%"; 153 | ha_add(&entity); 154 | 155 | memset(&entity, 0x00, sizeof(entity)); 156 | entity.id = "pwm_deviation"; 157 | entity.name = "PWM averaged deviation"; 158 | entity.state_class = "measurement"; 159 | entity.type = ha_sensor; 160 | entity.stat_t = "feeds/float/%s/pwm_deviation"; 161 | ha_add(&entity); 162 | 163 | memset(&entity, 0x00, sizeof(entity)); 164 | entity.id = "ticks_total"; 165 | entity.name = "Activity counter ticks total"; 166 | entity.state_class = "total_increasing"; 167 | entity.type = ha_sensor; 168 | entity.stat_t = "feeds/integer/%s/ticks_total"; 169 | entity.unit_of_meas = "counts"; 170 | ha_add(&entity); 171 | 172 | memset(&entity, 0x00, sizeof(entity)); 173 | entity.id = "counts_per_minute"; 174 | entity.name = "Activity"; 175 | entity.type = ha_sensor; 176 | entity.stat_t = "feeds/integer/%s/ticks"; 177 | entity.unit_of_meas = "cpm"; 178 | ha_add(&entity); 179 | 180 | memset(&entity, 0x00, sizeof(entity)); 181 | entity.id = "activity"; 182 | entity.name = "Dose current"; 183 | entity.type = ha_sensor; 184 | entity.stat_t = "feeds/float/%s/activity"; 185 | entity.unit_of_meas = "µSv/h"; 186 | ha_add(&entity); 187 | 188 | memset(&entity, 0x00, sizeof(entity)); 189 | entity.id = "activity_total"; 190 | entity.name = "Dose total"; 191 | entity.type = ha_sensor; 192 | entity.stat_t = "feeds/float/%s/activity_total"; 193 | entity.unit_of_meas = "µSv"; 194 | ha_add(&entity); 195 | 196 | memset(&entity, 0x00, sizeof(entity)); 197 | entity.id = "esp32_hall"; 198 | entity.name = "ESP32 hall sensor data"; 199 | entity.state_class = "measurement"; 200 | entity.type = ha_sensor; 201 | entity.stat_t = "feeds/float/%s/esp32_hall"; 202 | ha_add(&entity); 203 | 204 | memset(&entity, 0x00, sizeof(entity)); 205 | entity.id = "temperature"; 206 | entity.dev_class = "temperature"; 207 | entity.name = "BME280 Temperature"; 208 | entity.type = ha_sensor; 209 | entity.stat_t = "feeds/float/%s/temperature"; 210 | entity.unit_of_meas = "°C"; 211 | ha_add(&entity); 212 | 213 | memset(&entity, 0x00, sizeof(entity)); 214 | entity.id = "humidity"; 215 | entity.dev_class = "humidity"; 216 | entity.name = "BME280 Humidity"; 217 | entity.type = ha_sensor; 218 | entity.stat_t = "feeds/float/%s/humidity"; 219 | entity.unit_of_meas = "%"; 220 | ha_add(&entity); 221 | 222 | memset(&entity, 0x00, sizeof(entity)); 223 | entity.id = "pressure"; 224 | entity.dev_class = "pressure"; 225 | entity.name = "BME280 Pressure"; 226 | entity.type = ha_sensor; 227 | entity.stat_t = "feeds/float/%s/pressure"; 228 | entity.unit_of_meas = "hPa"; 229 | ha_add(&entity); 230 | 231 | memset(&entity, 0x00, sizeof(entity)); 232 | entity.id = "rssi"; 233 | entity.name = "WiFi RSSI"; 234 | entity.type = ha_sensor; 235 | entity.stat_t = "feeds/integer/%s/rssi"; 236 | entity.unit_of_meas = "dBm"; 237 | ha_add(&entity); 238 | 239 | memset(&entity, 0x00, sizeof(entity)); 240 | entity.id = "error"; 241 | entity.name = "Error message"; 242 | entity.type = ha_sensor; 243 | entity.stat_t = "feeds/string/%s/error"; 244 | ha_add(&entity); 245 | } 246 | 247 | void mqtt_publish_string(const char *name, const char *value) 248 | { 249 | char path_buffer[128]; 250 | 251 | sprintf(path_buffer, name, current_config.mqtt_client); 252 | 253 | if (!mqtt.publish(path_buffer, value)) 254 | { 255 | mqtt_fail = true; 256 | } 257 | Serial.printf("Published %s : %s\n", path_buffer, value); 258 | } 259 | 260 | void mqtt_publish_float(const char *name, float value) 261 | { 262 | char path_buffer[128]; 263 | char buffer[32]; 264 | 265 | sprintf(path_buffer, name, current_config.mqtt_client); 266 | sprintf(buffer, "%0.2f", value); 267 | 268 | if (!mqtt.publish(path_buffer, buffer)) 269 | { 270 | mqtt_fail = true; 271 | } 272 | Serial.printf("Published %s : %s\n", path_buffer, buffer); 273 | } 274 | 275 | void mqtt_publish_int(const char *name, uint32_t value) 276 | { 277 | char path_buffer[128]; 278 | char buffer[32]; 279 | 280 | if (value == 0x7FFFFFFF) 281 | { 282 | return; 283 | } 284 | sprintf(path_buffer, name, current_config.mqtt_client); 285 | sprintf(buffer, "%d", value); 286 | 287 | if (!mqtt.publish(path_buffer, buffer)) 288 | { 289 | mqtt_fail = true; 290 | } 291 | Serial.printf("Published %s : %s\n", path_buffer, buffer); 292 | } 293 | 294 | bool mqtt_loop() 295 | { 296 | uint32_t time = millis(); 297 | static uint32_t nextTime = 0; 298 | 299 | #ifdef TESTMODE 300 | return false; 301 | #endif 302 | if (mqtt_fail) 303 | { 304 | mqtt_fail = false; 305 | mqtt.disconnect(); 306 | } 307 | 308 | MQTT_connect(); 309 | 310 | if (!mqtt.connected()) 311 | { 312 | return false; 313 | } 314 | 315 | mqtt.loop(); 316 | 317 | ha_loop(); 318 | 319 | if (time >= nextTime) 320 | { 321 | bool do_publish = false; 322 | 323 | if ((time - mqtt_last_publish_time) > 60000) 324 | { 325 | do_publish = true; 326 | } 327 | 328 | if (do_publish) 329 | { 330 | mqtt_last_publish_time = time; 331 | int counts = det_fetch(); 332 | 333 | if ((current_config.mqtt_publish & 1) && pwm_is_stable()) 334 | { 335 | ticks_total += counts; 336 | mqtt_publish_int((char *)"feeds/integer/%s/ticks", counts); 337 | mqtt_publish_int((char *)"feeds/integer/%s/ticks_total", ticks_total); 338 | mqtt_publish_float((char *)"feeds/float/%s/activity", current_config.conv_usv_per_bq * counts); 339 | mqtt_publish_float((char *)"feeds/float/%s/activity_total", current_config.conv_usv_per_bq * ticks_total / 60.0f); 340 | } 341 | if (current_config.mqtt_publish & 2) 342 | { 343 | mqtt_publish_float((char *)"feeds/float/%s/voltage", adc_voltage_avg); 344 | mqtt_publish_float((char *)"feeds/float/%s/pwm_freq", pwm_freq); 345 | mqtt_publish_float((char *)"feeds/float/%s/pwm_value", pwm_value); 346 | mqtt_publish_float((char *)"feeds/float/%s/pwm_deviation", pwm_deviation); 347 | mqtt_publish_int((char *)"feeds/integer/%s/version", PIO_SRC_REVNUM); 348 | mqtt_publish_int((char *)"feeds/integer/%s/rssi", wifi_rssi); 349 | 350 | mqtt_publish_float((char *)"feeds/float/%s/esp32_hall", esp32_hall); 351 | 352 | if ((main_duration_max > 0) && (main_duration_max < 1000000) && (main_duration_min > 0) && (main_duration_min < 1000000)) 353 | { 354 | mqtt_publish_float((char *)"feeds/float/%s/main_duration", main_duration); 355 | mqtt_publish_float((char *)"feeds/float/%s/main_duration_min", main_duration_min); 356 | mqtt_publish_float((char *)"feeds/float/%s/main_duration_max", main_duration_max); 357 | mqtt_publish_float((char *)"feeds/float/%s/main_duration_avg", main_duration_avg); 358 | } 359 | if ((main_cycletime_max > 0) && (main_cycletime_max < 10000000) && (main_cycletime_min > 0) && (main_cycletime_min < 10000000)) 360 | { 361 | mqtt_publish_float((char *)"feeds/float/%s/main_cycletime", main_cycletime); 362 | mqtt_publish_float((char *)"feeds/float/%s/main_cycletime_min", main_cycletime_min); 363 | mqtt_publish_float((char *)"feeds/float/%s/main_cycletime_max", main_cycletime_max); 364 | mqtt_publish_float((char *)"feeds/float/%s/main_cycletime_avg", main_cycletime_avg); 365 | } 366 | main_duration_max = 0; 367 | main_duration_min = 1000000; 368 | main_cycletime_max = 0; 369 | main_cycletime_min = 10000000; 370 | } 371 | if (current_config.mqtt_publish & 4) 372 | { 373 | mqtt_publish_float((char *)"feeds/float/%s/temperature", bme280_temperature); 374 | mqtt_publish_float((char *)"feeds/float/%s/humidity", bme280_humidity); 375 | mqtt_publish_float((char *)"feeds/float/%s/pressure", bme280_pressure); 376 | } 377 | if (current_config.mqtt_publish & 8) 378 | { 379 | mqtt_publish_int((char *)"feeds/integer/%s/co2", ccs811_co2); 380 | mqtt_publish_int((char *)"feeds/integer/%s/tvoc", ccs811_tvoc); 381 | } 382 | } 383 | nextTime = time + 10000; 384 | } 385 | 386 | return false; 387 | } 388 | 389 | void MQTT_connect() 390 | { 391 | uint32_t curTime = millis(); 392 | int8_t ret; 393 | 394 | if (strlen(current_config.mqtt_server) == 0) 395 | { 396 | return; 397 | } 398 | 399 | mqtt.setServer(current_config.mqtt_server, current_config.mqtt_port); 400 | 401 | if (WiFi.status() != WL_CONNECTED) 402 | { 403 | return; 404 | } 405 | 406 | if (mqtt.connected()) 407 | { 408 | return; 409 | } 410 | 411 | if ((mqtt_lastConnect != 0) && (curTime - mqtt_lastConnect < (1000 << mqtt_retries))) 412 | { 413 | return; 414 | } 415 | 416 | mqtt_lastConnect = curTime; 417 | 418 | Serial.println("MQTT: Connecting to MQTT... "); 419 | 420 | sprintf(command_topic, "tele/%s/command", current_config.mqtt_client); 421 | sprintf(response_topic, "tele/%s/response", current_config.mqtt_client); 422 | 423 | ret = mqtt.connect(current_config.mqtt_client, current_config.mqtt_user, current_config.mqtt_password); 424 | 425 | if (ret == 0) 426 | { 427 | mqtt_retries++; 428 | if (mqtt_retries > 8) 429 | { 430 | mqtt_retries = 8; 431 | } 432 | Serial.printf("MQTT: (%d) ", mqtt.state()); 433 | Serial.println("MQTT: Retrying MQTT connection"); 434 | mqtt.disconnect(); 435 | } 436 | else 437 | { 438 | /* discard counts till then */ 439 | det_fetch(); 440 | Serial.println("MQTT Connected!"); 441 | mqtt.subscribe(command_topic); 442 | ha_connected(); 443 | mqtt_publish_string((char *)"feeds/string/%s/error", ""); 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /include/GxEPD2_display_selection_new_style.h: -------------------------------------------------------------------------------- 1 | // Display Library example for SPI e-paper panels from Dalian Good Display and boards from Waveshare. 2 | // Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! 3 | // 4 | // Display Library based on Demo Example from Good Display: http://www.e-paper-display.com/download_list/downloadcategoryid=34&isMode=false.html 5 | // 6 | // Author: Jean-Marc Zingg 7 | // 8 | // Version: see library.properties 9 | // 10 | // Library: https://github.com/ZinggJM/GxEPD2 11 | 12 | // Supporting Arduino Forum Topics: 13 | // Waveshare e-paper displays with SPI: http://forum.arduino.cc/index.php?topic=487007.0 14 | // Good Display ePaper for Arduino: https://forum.arduino.cc/index.php?topic=436411.0 15 | 16 | 17 | #define EPD_CS 14 18 | #define EPD_RST 22 19 | #define EPD_DC 19 20 | #define EPD_BUSY 21 21 | #define EPD_MOSI 23 22 | #define EPD_SCLK 33 23 | 24 | //#define EPD_CS 19 25 | //#define EPD_RST 21 26 | //#define EPD_DC 14 27 | //#define EPD_BUSY 33 28 | //#define EPD_MOSI 23 29 | //#define EPD_SCLK 22 30 | 31 | 32 | // select the display class (only one), matching the kind of display panel 33 | //#define GxEPD2_DISPLAY_CLASS GxEPD2_BW 34 | #define GxEPD2_DISPLAY_CLASS GxEPD2_3C 35 | //#define GxEPD2_DISPLAY_CLASS GxEPD2_7C 36 | 37 | // select the display driver class (only one) for your panel 38 | //#define GxEPD2_DRIVER_CLASS GxEPD2_102 // GxEPD2_102 80x128, UC8175 39 | //#define GxEPD2_DRIVER_CLASS GxEPD2_154 // GDEP015OC1 200x200, IL3829, no longer available 40 | //#define GxEPD2_DRIVER_CLASS GxEPD2_154_D67 // GDEH0154D67 200x200, SSD1681 41 | //#define GxEPD2_DRIVER_CLASS GxEPD2_154_T8 // GDEW0154T8 152x152, UC8151 (IL0373) 42 | //#define GxEPD2_DRIVER_CLASS GxEPD2_154_M09 // GDEW0154M09 200x200, JD79653A 43 | //#define GxEPD2_DRIVER_CLASS GxEPD2_154_M10 // GDEW0154M10 152x152, UC8151D 44 | //#define GxEPD2_DRIVER_CLASS GxEPD2_213 // GDE0213B1 128x250, IL3895, phased out 45 | //#define GxEPD2_DRIVER_CLASS GxEPD2_213_B72 // GDEH0213B72 128x250, SSD1675A (IL3897) 46 | //#define GxEPD2_DRIVER_CLASS GxEPD2_213_B73 // GDEH0213B73 128x250, SSD1675B 47 | //#define GxEPD2_DRIVER_CLASS GxEPD2_213_B74 // GDEM0213B74 128x250, SSD1680 48 | //#define GxEPD2_DRIVER_CLASS GxEPD2_213_flex // GDEW0213I5F 104x212, UC8151 (IL0373) 49 | //#define GxEPD2_DRIVER_CLASS GxEPD2_213_M21 // GDEW0213M21 104x212, UC8151 (IL0373) 50 | //#define GxEPD2_DRIVER_CLASS GxEPD2_213_T5D // GDEW0213T5D 104x212, UC8151D 51 | //#define GxEPD2_DRIVER_CLASS GxEPD2_290 // GDEH029A1 128x296, SSD1608 (IL3820) 52 | //#define GxEPD2_DRIVER_CLASS GxEPD2_290_T5 // GDEW029T5 128x296, UC8151 (IL0373) 53 | //#define GxEPD2_DRIVER_CLASS GxEPD2_290_T5D // GDEW029T5D 128x296, UC8151D 54 | //#define GxEPD2_DRIVER_CLASS GxEPD2_290_I6FD // GDEW029I6FD 128x296, UC8151D 55 | //#define GxEPD2_DRIVER_CLASS GxEPD2_290_T94 // GDEM029T94 128x296, SSD1680 56 | //#define GxEPD2_DRIVER_CLASS GxEPD2_290_T94_V2 // GDEM029T94 128x296, SSD1680, Waveshare 2.9" V2 variant 57 | //#define GxEPD2_DRIVER_CLASS GxEPD2_290_M06 // GDEW029M06 128x296, UC8151D 58 | //#define GxEPD2_DRIVER_CLASS GxEPD2_260 // GDEW026T0 152x296, UC8151 (IL0373) 59 | //#define GxEPD2_DRIVER_CLASS GxEPD2_260_M01 // GDEW026M01 152x296, UC8151 (IL0373) 60 | //#define GxEPD2_DRIVER_CLASS GxEPD2_270 // GDEW027W3 176x264, EK79652 (IL91874) 61 | //#define GxEPD2_DRIVER_CLASS GxEPD2_371 // GDEW0371W7 240x416, UC8171 (IL0324) 62 | //#define GxEPD2_DRIVER_CLASS GxEPD2_370_TC1 // ED037TC1 280x480, SSD1677, Waveshare 3.7" 63 | //#define GxEPD2_DRIVER_CLASS GxEPD2_420 // GDEW042T2 400x300, UC8176 (IL0398) 64 | //#define GxEPD2_DRIVER_CLASS GxEPD2_420_M01 // GDEW042M01 400x300, UC8176 (IL0398) 65 | //#define GxEPD2_DRIVER_CLASS GxEPD2_583 // GDEW0583T7 600x448, UC8179 (IL0371) 66 | //#define GxEPD2_DRIVER_CLASS GxEPD2_583_T8 // GDEW0583T8 648x480, GD7965 67 | //#define GxEPD2_DRIVER_CLASS GxEPD2_750 // GDEW075T8 640x384, UC8179 (IL0371) 68 | //#define GxEPD2_DRIVER_CLASS GxEPD2_750_T7 // GDEW075T7 800x480, GD7965 69 | //#define GxEPD2_DRIVER_CLASS GxEPD2_1160_T91 // GDEH116T91 960x640, SSD1677 70 | //#define GxEPD2_DRIVER_CLASS GxEPD2_1248 // GDEW1248T3 1303x984, UC8179 71 | // 3-color e-papers 72 | //#define GxEPD2_DRIVER_CLASS GxEPD2_154c // GDEW0154Z04 200x200, IL0376F, no longer available 73 | //#define GxEPD2_DRIVER_CLASS GxEPD2_154_Z90c // GDEH0154Z90 200x200, SSD1681 74 | //#define GxEPD2_DRIVER_CLASS GxEPD2_213c // GDEW0213Z16 104x212, UC8151 (IL0373) 75 | //#define GxEPD2_DRIVER_CLASS GxEPD2_213_Z19c // GDEW0213Z19 104x212, UC8151D 76 | //#define GxEPD2_DRIVER_CLASS GxEPD2_213_Z98c // GDEY0213Z98 122x250, SSD1680 77 | //#define GxEPD2_DRIVER_CLASS GxEPD2_290c // GDEW029Z10 128x296, UC8151 (IL0373) 78 | #define GxEPD2_DRIVER_CLASS GxEPD2_290_Z13c // GDEH029Z13 128x296, UC8151D <<--- 79 | //#define GxEPD2_DRIVER_CLASS GxEPD2_290_C90c // GDEM029C90 128x296, SSD1680 80 | //#define GxEPD2_DRIVER_CLASS GxEPD2_270c // GDEW027C44 176x264, IL91874 81 | //#define GxEPD2_DRIVER_CLASS GxEPD2_420c // GDEW042Z15 400x300, UC8176 (IL0398) 82 | //#define GxEPD2_DRIVER_CLASS GxEPD2_420c_Z21 // GDEQ042Z21 400x300, UC8276 83 | //#define GxEPD2_DRIVER_CLASS GxEPD2_583c // GDEW0583Z21 600x448, UC8179 (IL0371) 84 | //#define GxEPD2_DRIVER_CLASS GxEPD2_750c // GDEW075Z09 640x384, UC8179 (IL0371) 85 | // #define GxEPD2_DRIVER_CLASS GxEPD2_750c_Z08 // GDEW075Z08 800x480, GD7965 86 | //#define GxEPD2_DRIVER_CLASS GxEPD2_750c_Z90 // GDEH075Z90 880x528, SSD1677 87 | // 7-color e-paper 88 | //#define GxEPD2_DRIVER_CLASS GxEPD2_565c // Waveshare 5.65" 7-color (3C graphics) 89 | // grey levels parallel IF e-papers on Waveshare e-Paper IT8951 Driver HAT 90 | //#define GxEPD2_DRIVER_CLASS GxEPD2_it60 // ED060SCT 800x600 91 | //#define GxEPD2_DRIVER_CLASS GxEPD2_it60_1448x1072 // ED060KC1 1448x1072 92 | //#define GxEPD2_DRIVER_CLASS GxEPD2_it78_1872x1404 // ED078KC2 1872x1404 93 | 94 | // SS is usually used for CS. define here for easy change 95 | #ifndef EPD_CS 96 | #define EPD_CS SS 97 | #endif 98 | 99 | #if defined(GxEPD2_DISPLAY_CLASS) && defined(GxEPD2_DRIVER_CLASS) 100 | 101 | // somehow there should be an easier way to do this 102 | #define GxEPD2_BW_IS_GxEPD2_BW true 103 | #define GxEPD2_3C_IS_GxEPD2_3C true 104 | #define GxEPD2_7C_IS_GxEPD2_7C true 105 | #define GxEPD2_1248_IS_GxEPD2_1248 true 106 | #define IS_GxEPD(c, x) (c##x) 107 | #define IS_GxEPD2_BW(x) IS_GxEPD(GxEPD2_BW_IS_, x) 108 | #define IS_GxEPD2_3C(x) IS_GxEPD(GxEPD2_3C_IS_, x) 109 | #define IS_GxEPD2_7C(x) IS_GxEPD(GxEPD2_7C_IS_, x) 110 | #define IS_GxEPD2_1248(x) IS_GxEPD(GxEPD2_1248_IS_, x) 111 | 112 | #include "GxEPD2_selection_check.h" 113 | 114 | #if defined (ESP8266) 115 | #define MAX_DISPLAY_BUFFER_SIZE (81920ul-34000ul-5000ul) // ~34000 base use, change 5000 to your application use 116 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) 117 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) 118 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) 119 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) 120 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) 121 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) 122 | #endif 123 | // adapt the constructor parameters to your wiring 124 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4)); 125 | // mapping of Waveshare e-Paper ESP8266 Driver Board, new version 126 | //GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=15*/ EPD_CS, /*DC=4*/ 4, /*RST=2*/ 2, /*BUSY=5*/ 5)); 127 | // mapping of Waveshare e-Paper ESP8266 Driver Board, old version 128 | //GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=15*/ EPD_CS, /*DC=4*/ 4, /*RST=5*/ 5, /*BUSY=16*/ 16)); 129 | #undef MAX_DISPLAY_BUFFER_SIZE 130 | #undef MAX_HEIGHT 131 | #endif 132 | 133 | #if defined(ESP32) 134 | #define MAX_DISPLAY_BUFFER_SIZE 65536ul // e.g. 135 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) 136 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) 137 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) 138 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) 139 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) 140 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) 141 | #endif 142 | // adapt the constructor parameters to your wiring 143 | #if !IS_GxEPD2_1248(GxEPD2_DRIVER_CLASS) 144 | #if defined(ARDUINO_LOLIN_D32_PRO) 145 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 0, /*RST=*/ 2, /*BUSY=*/ 15)); 146 | #elif defined(ARDUINO_ESP32_DEV) // e.g. TTGO T8 ESP32-WROVER 147 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 2, /*RST=*/ 0, /*BUSY=*/ 4)); 148 | #else 149 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ EPD_DC, /*RST=*/ EPD_RST, /*BUSY=*/ EPD_BUSY)); 150 | #endif 151 | #else // GxEPD2_1248 152 | // Waveshare 12.48 b/w SPI display board and frame or Good Display 12.48 b/w panel GDEW1248T3 153 | // general constructor for use with all parameters, e.g. for Waveshare ESP32 driver board mounted on connection board 154 | GxEPD2_BW < GxEPD2_1248, GxEPD2_1248::HEIGHT / 4 > display(GxEPD2_1248(/*sck=*/ 13, /*miso=*/ 12, /*mosi=*/ 14, 155 | /*cs_m1=*/ 23, /*cs_s1=*/ 22, /*cs_m2=*/ 16, /*cs_s2=*/ 19, 156 | /*dc1=*/ 25, /*dc2=*/ 17, /*rst1=*/ 33, /*rst2=*/ 5, 157 | /*busy_m1=*/ 32, /*busy_s1=*/ 26, /*busy_m2=*/ 18, /*busy_s2=*/ 4)); 158 | #endif 159 | #undef MAX_DISPLAY_BUFFER_SIZE 160 | #undef MAX_HEIGHT 161 | #endif 162 | 163 | // can't use package "STMF1 Boards (STM32Duino.com)" (Roger Clark) anymore with Adafruit_GFX, use "STM32 Boards (selected from submenu)" (STMicroelectronics) 164 | #if defined(ARDUINO_ARCH_STM32) 165 | #define MAX_DISPLAY_BUFFER_SIZE 15000ul // ~15k is a good compromise 166 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) 167 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) 168 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) 169 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) 170 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) 171 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) 172 | #endif 173 | // adapt the constructor parameters to your wiring 174 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=PA4*/ EPD_CS, /*DC=*/ PA3, /*RST=*/ PA2, /*BUSY=*/ PA1)); 175 | #undef MAX_DISPLAY_BUFFER_SIZE 176 | #undef MAX_HEIGHT 177 | #endif 178 | 179 | #if defined(__AVR) 180 | #if defined (ARDUINO_AVR_MEGA2560) // Note: SS is on 53 on MEGA 181 | #define MAX_DISPLAY_BUFFER_SIZE 5000 // e.g. full height for 200x200 182 | #else // Note: SS is on 10 on UNO, NANO 183 | #define MAX_DISPLAY_BUFFER_SIZE 800 // 184 | #endif 185 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) 186 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) 187 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) 188 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) 189 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) 190 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) 191 | #endif 192 | // adapt the constructor parameters to your wiring 193 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); 194 | #endif 195 | 196 | #if defined(ARDUINO_ARCH_SAM) 197 | #define MAX_DISPLAY_BUFFER_SIZE 32768ul // e.g., up to 96k 198 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) 199 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) 200 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) 201 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) 202 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) 203 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) 204 | #endif 205 | // adapt the constructor parameters to your wiring 206 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=10*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); 207 | #undef MAX_DISPLAY_BUFFER_SIZE 208 | #undef MAX_HEIGHT 209 | #endif 210 | 211 | #if defined(ARDUINO_ARCH_SAMD) 212 | #define MAX_DISPLAY_BUFFER_SIZE 15000ul // ~15k is a good compromise 213 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) 214 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) 215 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) 216 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) 217 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) 218 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) 219 | #endif 220 | // adapt the constructor parameters to your wiring 221 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 4, /*DC=*/ 7, /*RST=*/ 6, /*BUSY=*/ 5)); 222 | //GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 4, /*DC=*/ 3, /*RST=*/ 2, /*BUSY=*/ 1)); // my Seed XIOA0 223 | //GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 3, /*DC=*/ 2, /*RST=*/ 1, /*BUSY=*/ 0)); // my other Seed XIOA0 224 | #undef MAX_DISPLAY_BUFFER_SIZE 225 | #undef MAX_HEIGHT 226 | #endif 227 | 228 | #if defined(ARDUINO_ARCH_RP2040) 229 | #define MAX_DISPLAY_BUFFER_SIZE 131072ul // e.g. half of available ram 230 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) 231 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) 232 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) 233 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) 234 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) 235 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) 236 | #endif 237 | #if defined(ARDUINO_NANO_RP2040_CONNECT) 238 | // adapt the constructor parameters to your wiring 239 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); 240 | #endif 241 | #if defined(ARDUINO_RASPBERRY_PI_PICO) 242 | // adapt the constructor parameters to your wiring 243 | //GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); // my proto board 244 | // mapping of GoodDisplay DESPI-PICO. NOTE: uses alternate HW SPI pins! 245 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 3, /*DC=*/ 2, /*RST=*/ 1, /*BUSY=*/ 0)); // DESPI-PICO 246 | //GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 3, /*DC=*/ 2, /*RST=*/ 11, /*BUSY=*/ 10)); // DESPI-PICO modified 247 | #endif 248 | #undef MAX_DISPLAY_BUFFER_SIZE 249 | #undef MAX_HEIGHT 250 | #endif 251 | 252 | #endif -------------------------------------------------------------------------------- /src/EPD.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #define ENABLE_GxEPD2_GFX 0 5 | #define MIN(a,b) ((a)<=(b)?(a):(b)) 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // select the display class and display driver class in the following file (new style): 14 | #include "GxEPD2_display_selection_new_style.h" 15 | 16 | U8G2_FOR_ADAFRUIT_GFX u8g2Fonts; 17 | 18 | const char epd_logo_string[] = "Geiger v3.1"; 19 | const char epd_logo_copyright[] = "\xA9 g3gg0.de"; 20 | #define geiger_width 128 21 | #define geiger_height 140 22 | 23 | 24 | uint32_t epd_lasttime = 0; 25 | extern uint32_t det_counts_last; 26 | 27 | 28 | /* https://www.flaticon.com/free-icon/geiger-counter_3325167 */ 29 | 30 | static unsigned char geiger_bw[] = { 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 41 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 43 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 44 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 46 | 0x00, 0x00, 0xb6, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x02, 0x00, 0x00, 47 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x49, 0x44, 0x44, 0x44, 0x44, 0x44, 48 | 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x4a, 0x11, 49 | 0x11, 0x11, 0x11, 0x11, 0x91, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 50 | 0x00, 0x40, 0x15, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x02, 0x00, 0x00, 51 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x55, 0x48, 0x48, 0x48, 0x48, 0x48, 52 | 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x85, 0x22, 53 | 0x21, 0x21, 0x21, 0x21, 0x51, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x00, 0x40, 0x2d, 0x24, 0x0a, 0x25, 0x0a, 0x45, 0x42, 0x01, 0x00, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x92, 0x90, 0x50, 0x88, 0x50, 0x08, 56 | 0x09, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x95, 0x12, 57 | 0x05, 0x21, 0x05, 0x29, 0xa9, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | 0x00, 0x40, 0x25, 0x44, 0xa8, 0x94, 0x50, 0x42, 0x04, 0x05, 0x00, 0x00, 59 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x4a, 0x91, 0x02, 0x01, 0x45, 0x88, 60 | 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x0a, 0x4a, 61 | 0x54, 0x2a, 0x88, 0x12, 0x05, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 62 | 0x00, 0xc0, 0xaa, 0x80, 0x40, 0x40, 0x29, 0x52, 0x28, 0x01, 0x00, 0x00, 63 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x15, 0x2a, 0x15, 0x15, 0x42, 0x88, 64 | 0x42, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xa5, 0x20, 65 | 0x40, 0xa2, 0x48, 0x11, 0x4a, 0x00, 0xc0, 0x0b, 0x00, 0x00, 0x00, 0x00, 66 | 0x00, 0x40, 0x15, 0x45, 0x15, 0x09, 0x05, 0xa2, 0x10, 0x05, 0x40, 0x15, 67 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x55, 0x14, 0x22, 0x51, 0x50, 0x0a, 68 | 0x45, 0x00, 0x80, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x0a, 0x41, 69 | 0x14, 0x0a, 0x45, 0x20, 0x90, 0x06, 0xc0, 0x4a, 0x00, 0x00, 0x00, 0x00, 70 | 0x00, 0x40, 0x55, 0x4a, 0x41, 0x50, 0x14, 0xa5, 0x12, 0x00, 0x40, 0x2b, 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x09, 0x11, 0x2a, 0x85, 0xa0, 0x08, 72 | 0xa2, 0x06, 0xc0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x2a, 0xa2, 73 | 0x40, 0x90, 0x0a, 0xa2, 0x08, 0x00, 0x80, 0x35, 0x00, 0x00, 0x00, 0x00, 74 | 0x00, 0x40, 0x95, 0x04, 0x95, 0x0a, 0x50, 0x09, 0xa5, 0x06, 0xc0, 0x26, 75 | 0x00, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x95, 0x54, 0x10, 0x50, 0x05, 0x52, 76 | 0x08, 0x04, 0x80, 0x2d, 0x00, 0x00, 0x00, 0xf8, 0x07, 0x00, 0x55, 0xa9, 77 | 0x66, 0xab, 0x6a, 0x95, 0xb5, 0x02, 0xc0, 0x2a, 0x00, 0x00, 0x00, 0xf8, 78 | 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2b, 79 | 0x00, 0x00, 0x00, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 80 | 0x00, 0x00, 0xc0, 0x56, 0x00, 0x00, 0x00, 0xf8, 0x07, 0x00, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x15, 0x00, 0x00, 0xf0, 0xff, 82 | 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x56, 83 | 0x00, 0x00, 0xfe, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 84 | 0x00, 0x00, 0xc0, 0x2a, 0x00, 0x80, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 85 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x0b, 0x00, 0xe0, 0xff, 0xff, 86 | 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x15, 87 | 0x00, 0xf8, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 88 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 90 | 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 91 | 0x00, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 92 | 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x7f, 0xfc, 0x07, 0x00, 0x00, 0x00, 93 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x1f, 0xfc, 94 | 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 95 | 0xc0, 0xff, 0x03, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 96 | 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x03, 0xf8, 0x07, 0x00, 0x00, 0x00, 97 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff, 0x00, 0xe0, 98 | 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 99 | 0xf0, 0x7f, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 100 | 0x00, 0x00, 0xc0, 0x2d, 0xf0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 101 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x13, 0xf8, 0x3f, 0x00, 0x00, 102 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x2e, 103 | 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 104 | 0x00, 0x00, 0x40, 0x53, 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 105 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x2e, 0xfc, 0x0f, 0x00, 0x00, 106 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x15, 107 | 0xfc, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 108 | 0x00, 0x00, 0xc0, 0x2b, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 109 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x55, 0xfc, 0x07, 0x00, 0x00, 110 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x17, 111 | 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 112 | 0x00, 0x00, 0xc0, 0x2a, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 113 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0xfe, 0x03, 0x00, 0x00, 114 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 115 | 0xfe, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 116 | 0x00, 0x00, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 117 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00, 118 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 119 | 0xfe, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 120 | 0x00, 0x00, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 121 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00, 122 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 123 | 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 124 | 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 125 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0x00, 0x00, 126 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 127 | 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 128 | 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 129 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0x00, 0x00, 130 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 131 | 0xfc, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 132 | 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 133 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x1f, 0x00, 0x00, 134 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 135 | 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 136 | 0x00, 0x00, 0x00, 0x00, 0xf0, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 137 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 138 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 139 | 0xf0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 140 | 0x00, 0x00, 0x00, 0x00, 0xe0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 141 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 142 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 143 | 0xe0, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 144 | 0x00, 0x00, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 145 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00, 146 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 147 | 0xc0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 148 | 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 149 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 150 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 151 | 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 152 | 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 153 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x01, 0x00, 154 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 155 | 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 156 | 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 157 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, 158 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 159 | 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 160 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 161 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x01, 0x00, 162 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 163 | 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 164 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 165 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, 166 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 167 | 0x80, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 168 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 169 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 170 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 171 | 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 172 | 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 173 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 174 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 175 | 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 176 | 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 177 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 178 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 179 | 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 180 | 0x00, 0x00, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 181 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00, 182 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 183 | 0xc0, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 184 | 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 185 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00, 186 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 187 | 0xc0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 188 | 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 189 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 190 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 191 | 0x80, 0xff, 0x01, 0x00, 0x00, 0x24, 0x22, 0x22, 0x22, 0x92, 0xff, 0xff, 192 | 0x7f, 0x11, 0x01, 0x00, 0x80, 0xff, 0x01, 0x00, 0x00, 0x55, 0x55, 0x55, 193 | 0xd5, 0x2a, 0xfc, 0xff, 0xdf, 0xaa, 0x16, 0x00, 0x00, 0xff, 0x03, 0x00, 194 | 0x40, 0xaa, 0xaa, 0xaa, 0x2a, 0xd5, 0xfb, 0xff, 0xbf, 0xaa, 0x0a, 0x00, 195 | 0x00, 0xff, 0x07, 0x00, 0x80, 0xaa, 0xaa, 0xaa, 0xaa, 0x2a, 0xfb, 0xff, 196 | 0xdf, 0xaa, 0x14, 0x00, 0x00, 0xfe, 0x1f, 0x00, 0x60, 0x55, 0x55, 0x55, 197 | 0x55, 0xa9, 0xde, 0xb6, 0xbd, 0xaa, 0x2a, 0x00, 0x00, 0xfe, 0x7f, 0x00, 198 | 0x44, 0x49, 0x49, 0xa5, 0x94, 0xaa, 0xfa, 0xff, 0x7f, 0xa5, 0x2a, 0x00, 199 | 0x00, 0xfc, 0xff, 0xff, 0x5f, 0x55, 0x55, 0xaa, 0xaa, 0x52, 0xff, 0xff, 200 | 0xd7, 0xaa, 0x0a, 0x00, 0x00, 0xf8, 0xff, 0xff, 0x5f, 0x55, 0x55, 0x55, 201 | 0x55, 0x55, 0xf5, 0xff, 0x5f, 0x95, 0x2a, 0x00, 0x00, 0xf8, 0xff, 0xff, 202 | 0x4f, 0x95, 0xaa, 0x4a, 0xa9, 0xaa, 0xbe, 0x6d, 0x7f, 0x55, 0x15, 0x00, 203 | 0x00, 0xe0, 0xff, 0xff, 0x5f, 0xa9, 0xaa, 0xaa, 0x2a, 0xa5, 0xfa, 0xff, 204 | 0x6f, 0x55, 0x2a, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xaf, 0x2a, 0x49, 0xaa, 205 | 0x52, 0x55, 0xfb, 0xff, 0xbf, 0xaa, 0x2a, 0x00, 0x00, 0x80, 0xff, 0xff, 206 | 0xaf, 0xaa, 0xaa, 0x2a, 0x55, 0x55, 0xfb, 0xff, 0xfe, 0x52, 0x25, 0x00, 207 | 0x00, 0x00, 0xfe, 0xff, 0xbf, 0xaa, 0xaa, 0x54, 0x55, 0xaa, 0xde, 0xf6, 208 | 0x57, 0x55, 0x2a, 0x00, 0x00, 0x00, 0xf8, 0xff, 0x6f, 0x55, 0x55, 0x55, 209 | 0x55, 0xa5, 0xfa, 0xef, 0xdf, 0xaa, 0x2a, 0x00, 0x00, 0x00, 0xc0, 0xff, 210 | 0xdf, 0x55, 0xb5, 0xd5, 0x6a, 0x5d, 0xff, 0xff, 0xbf, 0x6a, 0x1d, 0x00, 211 | 0x00, 0x00, 0x00, 0x00, 0xa0, 0xfd, 0xaf, 0xdd, 0xde, 0xeb, 0xfa, 0xff, 212 | 0xbf, 0xdf, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xa6, 0x7a, 0x6b, 213 | 0xab, 0xb6, 0xfd, 0xff, 0x7f, 0xb5, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 214 | 0x80, 0x6d, 0xd5, 0xde, 0xba, 0x6d, 0xfb, 0xff, 0xbf, 0xd5, 0x2d, 0x00, 215 | 0x00, 0x00, 0x00, 0x00, 0x40, 0xdb, 0x5b, 0xb5, 0xd7, 0xd6, 0xfe, 0xff, 216 | 0xdf, 0xbe, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb4, 0xf6, 0xb7, 217 | 0xba, 0xdd, 0xfa, 0xff, 0xdf, 0xea, 0x06, 0x00 }; 218 | 219 | static unsigned char geiger_rw[] = { 220 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 221 | 0xad, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0xb5, 0x6a, 222 | 0xb5, 0x6a, 0xb5, 0x6a, 0xb5, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 223 | 0x00, 0xad, 0x6d, 0x5b, 0x6b, 0x5b, 0x6b, 0x5b, 0x6b, 0xad, 0x01, 0x00, 224 | 0x00, 0x00, 0x00, 0x00, 0x40, 0x5b, 0xd5, 0xd6, 0xda, 0xd6, 0xda, 0x6a, 225 | 0xad, 0x55, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x6a, 0xab, 0x5a, 226 | 0x57, 0xad, 0x55, 0xd7, 0x5a, 0x5b, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 227 | 0x40, 0xdb, 0x6e, 0x6b, 0xd5, 0x5a, 0x5b, 0xb5, 0xd6, 0xb6, 0x0d, 0x00, 228 | 0x00, 0x00, 0x00, 0x00, 0xb0, 0x56, 0xd5, 0x5a, 0xad, 0xb5, 0xb6, 0xd6, 229 | 0xb5, 0x6a, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xd5, 0x5a, 0x6b, 230 | 0xbb, 0xd6, 0xea, 0x5a, 0xad, 0x56, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 231 | 0x50, 0x5d, 0xab, 0xd6, 0xaa, 0xb5, 0x55, 0xab, 0x75, 0x6d, 0x1b, 0x00, 232 | 0x00, 0x00, 0x00, 0x00, 0x68, 0x6b, 0xed, 0x5a, 0x6b, 0xad, 0xb6, 0xb6, 233 | 0xd6, 0x5a, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xda, 0x9a, 0xb5, 234 | 0x56, 0x6b, 0x6d, 0x6d, 0x55, 0x6b, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 235 | 0x50, 0xab, 0x22, 0x44, 0x48, 0x08, 0x01, 0x01, 0x49, 0x54, 0x2d, 0x00, 236 | 0x00, 0x00, 0x00, 0x00, 0x68, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 237 | 0x00, 0xec, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x5b, 0x00, 0x00, 238 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 239 | 0xd0, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x2a, 0x00, 240 | 0x00, 0x00, 0x00, 0x00, 0xb0, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 241 | 0x00, 0xa8, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x55, 0x00, 0x00, 242 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 243 | 0xb0, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x2d, 0x00, 244 | 0x00, 0x00, 0x00, 0x00, 0xd0, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 245 | 0x00, 0xa8, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x75, 0x00, 0x00, 246 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 247 | 0xd8, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x2b, 0x00, 248 | 0x00, 0x00, 0x00, 0x00, 0xb0, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 249 | 0x00, 0x50, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x5b, 0x00, 0x00, 250 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 251 | 0xd0, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x35, 0x00, 252 | 0x00, 0x00, 0x00, 0x00, 0xa8, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 253 | 0x00, 0xac, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x56, 0x00, 0x00, 254 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 255 | 0x48, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x2b, 0x00, 256 | 0x00, 0x00, 0x00, 0x00, 0xd0, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 257 | 0x00, 0xb0, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x2b, 0x00, 0x00, 258 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 259 | 0x68, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x2d, 0x00, 260 | 0x00, 0x00, 0x00, 0x00, 0xd0, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 261 | 0x00, 0x6c, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x5a, 0x00, 0x00, 262 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 263 | 0x68, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x2b, 0x00, 264 | 0x00, 0x00, 0x00, 0x00, 0xd8, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 265 | 0x00, 0x54, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0xad, 0x89, 0x88, 266 | 0x24, 0x49, 0x12, 0x21, 0x04, 0xb5, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 267 | 0xb8, 0x6a, 0xbb, 0xbb, 0x6d, 0xdb, 0x76, 0xef, 0x7d, 0xad, 0x36, 0x00, 268 | 0x00, 0x00, 0x00, 0x00, 0xa8, 0xad, 0x56, 0x55, 0xd5, 0xaa, 0x55, 0x55, 269 | 0xd5, 0xda, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x6b, 0xd5, 0xb6, 270 | 0xad, 0xb6, 0xd6, 0x5a, 0xab, 0xb5, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 271 | 0x58, 0xad, 0xad, 0x55, 0xdb, 0xaa, 0xad, 0xb5, 0xb6, 0xd6, 0x2a, 0x00, 272 | 0x00, 0x00, 0x00, 0x00, 0x68, 0x5b, 0xbb, 0x76, 0xb5, 0xb6, 0x6a, 0x6d, 273 | 0xad, 0xad, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x6a, 0x55, 0xcd, 274 | 0x6a, 0x6b, 0x5b, 0xab, 0xb5, 0x5a, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 275 | 0x58, 0xdb, 0xd6, 0x5a, 0x57, 0xad, 0xd6, 0x6a, 0xab, 0xd5, 0x36, 0x00, 276 | 0x00, 0x00, 0x00, 0x00, 0x68, 0x55, 0x6d, 0x6b, 0xed, 0xda, 0x5a, 0x57, 277 | 0x6d, 0xbb, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xdb, 0xaa, 0xda, 278 | 0xaa, 0x56, 0x6b, 0x6d, 0xab, 0xaa, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 279 | 0xd8, 0x56, 0xdb, 0x56, 0x6d, 0xa9, 0xac, 0x5a, 0xdd, 0xd6, 0x16, 0x00, 280 | 0x00, 0x00, 0x00, 0x00, 0xa8, 0xda, 0xb6, 0xb5, 0x15, 0x81, 0xd0, 0xd6, 281 | 0xaa, 0xb5, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0xb5, 0xaa, 0x56, 282 | 0x23, 0x24, 0x82, 0x5a, 0x5b, 0xad, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 283 | 0xa8, 0xad, 0xb6, 0xb5, 0x8a, 0x44, 0x28, 0xd4, 0x6a, 0x6b, 0x35, 0x00, 284 | 0x00, 0x00, 0x00, 0x00, 0xb8, 0xea, 0x6a, 0x6d, 0x50, 0x10, 0x85, 0xba, 285 | 0xd6, 0xda, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0xad, 0xad, 0xd5, 286 | 0x05, 0x22, 0x20, 0xd4, 0x5a, 0xab, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 287 | 0x78, 0xab, 0x6a, 0x5b, 0xa9, 0x88, 0x8a, 0x56, 0xab, 0x56, 0x35, 0x00, 288 | 0x00, 0x00, 0x00, 0x00, 0xa8, 0xb6, 0xad, 0x6a, 0x13, 0x11, 0x20, 0xda, 289 | 0xb6, 0x75, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x6a, 0x6b, 0x5b, 290 | 0xa5, 0x40, 0x85, 0xb5, 0x5a, 0xad, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 291 | 0xb0, 0xad, 0xda, 0xd6, 0x0a, 0x0a, 0x28, 0x57, 0x6b, 0xab, 0x35, 0x00, 292 | 0x00, 0x00, 0x00, 0x00, 0x50, 0xdb, 0x56, 0xb5, 0xa5, 0x50, 0xc2, 0x6c, 293 | 0xad, 0xb6, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xaa, 0xd5, 0x56, 294 | 0x97, 0x42, 0x88, 0x5b, 0x6b, 0xad, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 295 | 0xb8, 0x6d, 0xad, 0x6d, 0x4d, 0x04, 0x61, 0x6a, 0xdd, 0x5a, 0x2b, 0x00, 296 | 0x00, 0x00, 0x00, 0x00, 0x50, 0x5b, 0xbb, 0xaa, 0x9a, 0x74, 0xca, 0xd6, 297 | 0xaa, 0xb5, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xaa, 0xaa, 0x6d, 298 | 0xab, 0xab, 0xb1, 0xad, 0xb5, 0xd6, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 299 | 0xa0, 0x6d, 0xab, 0xda, 0xb6, 0x5a, 0x6f, 0xb5, 0xd6, 0x5a, 0x2d, 0x00, 300 | 0x00, 0x00, 0x00, 0x00, 0x78, 0xdb, 0xb6, 0x55, 0xad, 0xb6, 0xaa, 0xad, 301 | 0xad, 0xb5, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x55, 0xb5, 0xb6, 302 | 0xb5, 0xd5, 0x5a, 0xb5, 0x5a, 0xad, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 303 | 0xd0, 0xb6, 0x6d, 0xad, 0x6a, 0xad, 0xb5, 0x6d, 0xb5, 0xb5, 0x2d, 0x00, 304 | 0x00, 0x00, 0x00, 0x00, 0xb0, 0x55, 0xab, 0xd5, 0xae, 0xb5, 0x56, 0xab, 305 | 0x6b, 0xad, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0xb6, 0x5a, 0x5b, 306 | 0x55, 0x85, 0xda, 0x5a, 0xad, 0xb5, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 307 | 0xb0, 0xd5, 0xd6, 0x6a, 0xdb, 0x24, 0xb4, 0xd6, 0x5a, 0x6b, 0x2b, 0x00, 308 | 0x00, 0x00, 0x00, 0x00, 0x50, 0x5b, 0xad, 0xd6, 0x2a, 0x91, 0xaa, 0xb5, 309 | 0xb5, 0x56, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0xd6, 0xba, 0x5d, 310 | 0x7b, 0x02, 0x68, 0xad, 0xd6, 0xda, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 311 | 0x50, 0x6d, 0x55, 0xd5, 0x26, 0xa9, 0x54, 0x6b, 0xb5, 0x56, 0x1b, 0x00, 312 | 0x00, 0x00, 0x00, 0x00, 0x50, 0xab, 0x6d, 0x5b, 0x2d, 0x82, 0x60, 0xad, 313 | 0xad, 0xb5, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0xb6, 0x5a, 0xd5, 314 | 0x5a, 0x14, 0x5a, 0x5b, 0x6b, 0x6d, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 315 | 0xd0, 0x6d, 0x4b, 0x95, 0x55, 0x41, 0xd0, 0x52, 0x88, 0xd5, 0x36, 0x00, 316 | 0x00, 0x00, 0x00, 0x00, 0x30, 0xd5, 0x22, 0x40, 0x1b, 0x2a, 0xb1, 0x06, 317 | 0x25, 0x5b, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xad, 0x4a, 0x0a, 318 | 0x75, 0x22, 0xa2, 0x55, 0x00, 0xd6, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 319 | 0x58, 0xdb, 0x22, 0x50, 0x2b, 0x89, 0x5c, 0x01, 0x55, 0xb5, 0x2d, 0x00, 320 | 0x00, 0x00, 0x00, 0x00, 0x50, 0x55, 0x8b, 0x42, 0x56, 0x91, 0xb0, 0x2b, 321 | 0x80, 0x56, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x6d, 0x25, 0x08, 322 | 0x6d, 0x4a, 0x6e, 0x41, 0x95, 0x6d, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 323 | 0x60, 0x5b, 0x4b, 0xa1, 0x5a, 0x91, 0x54, 0x0b, 0x10, 0xd5, 0x2a, 0x00, 324 | 0x00, 0x00, 0x00, 0x00, 0x58, 0xb5, 0x12, 0x0a, 0xea, 0xa6, 0xb6, 0xa0, 325 | 0xc4, 0x56, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xeb, 0xa6, 0x90, 326 | 0xac, 0xb6, 0xad, 0x85, 0x88, 0xb5, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 327 | 0xd0, 0x56, 0x4b, 0x42, 0xd4, 0x6a, 0x35, 0x10, 0xa2, 0x6e, 0x15, 0x00, 328 | 0x00, 0x00, 0x00, 0x00, 0xb0, 0x5a, 0x8d, 0x88, 0x50, 0x5b, 0x6b, 0x45, 329 | 0x44, 0xd5, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xb6, 0x15, 0x21, 330 | 0x6a, 0xd5, 0x16, 0x10, 0xd1, 0x5a, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 331 | 0xd0, 0xaa, 0x36, 0x45, 0x40, 0x5b, 0xad, 0x22, 0x50, 0x6b, 0x2b, 0x00, 332 | 0x00, 0x00, 0x00, 0x00, 0xa8, 0x6d, 0x4d, 0x04, 0xd5, 0xd6, 0x4a, 0x84, 333 | 0xd2, 0xd6, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x6b, 0xbb, 0x54, 334 | 0xa0, 0x5a, 0x9b, 0x14, 0x52, 0xad, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 335 | 0x50, 0xad, 0x2a, 0x01, 0xb5, 0xb5, 0x36, 0x41, 0xb8, 0xb5, 0x15, 0x00, 336 | 0x00, 0x00, 0x00, 0x00, 0x68, 0x6b, 0x6b, 0x2a, 0xa8, 0xd6, 0x6a, 0x8a, 337 | 0x6a, 0xad, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xd6, 0xd6, 0x48, 338 | 0xd9, 0x5a, 0x2b, 0x11, 0x54, 0x6b, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 339 | 0x50, 0x5d, 0xad, 0x13, 0x54, 0x6b, 0xed, 0xa4, 0xda, 0x5a, 0x2b, 0x00, 340 | 0x00, 0x00, 0x00, 0x00, 0xd8, 0x6a, 0xb5, 0xa4, 0xda, 0x5a, 0x9b, 0x12, 341 | 0xab, 0xb6, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xab, 0xad, 0x8b, 342 | 0x54, 0x6b, 0xb5, 0xa2, 0xda, 0xea, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 343 | 0x50, 0x6d, 0x6b, 0x16, 0xbb, 0xd6, 0xd6, 0xaa, 0xab, 0x55, 0x15, 0x00, 344 | 0x00, 0x00, 0x00, 0x00, 0x68, 0xab, 0xda, 0x75, 0x6a, 0xad, 0xb5, 0x52, 345 | 0x6d, 0x5d, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xb6, 0x55, 0xcd, 346 | 0xd5, 0x5a, 0xad, 0x6d, 0x5b, 0x6b, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 347 | 0x50, 0xad, 0xb6, 0x3a, 0x5b, 0x6b, 0x6b, 0xab, 0x6a, 0xd5, 0x36, 0x00, 348 | 0x00, 0x00, 0x00, 0x00, 0x68, 0x6b, 0xad, 0xd5, 0xd6, 0xda, 0x5a, 0x6d, 349 | 0xdb, 0xb6, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x5a, 0x6b, 0xab, 350 | 0x5a, 0xab, 0xd6, 0xda, 0xaa, 0xad, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 351 | 0x50, 0xeb, 0x5a, 0x6d, 0x6b, 0x6d, 0xad, 0x55, 0x5b, 0xb5, 0x2d, 0x00, 352 | 0x00, 0x00, 0x00, 0x00, 0x60, 0xad, 0x6a, 0xdb, 0x5a, 0xab, 0xb5, 0xb6, 353 | 0x6a, 0x6b, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x6b, 0xdb, 0xaa, 354 | 0xb5, 0x5a, 0xad, 0xd5, 0xd6, 0x56, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 355 | 0x80, 0xd6, 0xaa, 0xb6, 0xd6, 0xb6, 0xb5, 0xb6, 0xad, 0xda, 0x0a, 0x00, 356 | 0x00, 0x00, 0x00, 0x00, 0x80, 0x5a, 0xdb, 0xaa, 0xb5, 0x55, 0x6b, 0xad, 357 | 0xda, 0x56, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0xaa, 0x6d, 358 | 0xad, 0x76, 0xad, 0xd5, 0x56, 0xb5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 359 | 0x00, 0xa0, 0xb6, 0x5a, 0x6b, 0xad, 0xb5, 0xb6, 0xda, 0x0a, 0x00, 0x00, 360 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x6b, 0xad, 0x55, 0x6b, 0xad, 361 | 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 362 | 0x55, 0x6d, 0xad, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 363 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 364 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 365 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 366 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 367 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 368 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 369 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 370 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 371 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 372 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 373 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 374 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 375 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 376 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 377 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 378 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 379 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 380 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 381 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 382 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 383 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 384 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 385 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 386 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 387 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 388 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 389 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 390 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 391 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 392 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 393 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 394 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 395 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 396 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 397 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 398 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 399 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 400 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 401 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 402 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 403 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 404 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 405 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 406 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 407 | 408 | 409 | void epd_show_logo() 410 | { 411 | u8g2Fonts.setFontMode(1); // use u8g2 transparent mode (this is default) 412 | u8g2Fonts.setFontDirection(0); // left to right (this is default) 413 | u8g2Fonts.setForegroundColor(GxEPD_BLACK); // apply Adafruit GFX color 414 | u8g2Fonts.setBackgroundColor(GxEPD_WHITE); // apply Adafruit GFX color 415 | u8g2Fonts.setFont(u8g2_font_helvR14_tf); // select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall 416 | int16_t tw = u8g2Fonts.getUTF8Width(epd_logo_string); // text box width 417 | int16_t ta = u8g2Fonts.getFontAscent(); // positive 418 | int16_t td = u8g2Fonts.getFontDescent(); // negative; in mathematicians view 419 | int16_t th = ta - td; // text box height 420 | 421 | uint16_t x = (display.width() - tw) / 2; 422 | uint16_t y = 20; 423 | 424 | display.firstPage(); 425 | 426 | display.fillScreen(GxEPD_WHITE); 427 | display.drawXBitmap(0, y, geiger_bw, geiger_width, geiger_height, GxEPD_BLACK); 428 | display.drawXBitmap(0, y, geiger_rw, geiger_width, geiger_height, GxEPD_RED); 429 | 430 | y += geiger_height + 20; 431 | u8g2Fonts.setCursor(x, y); 432 | u8g2Fonts.setForegroundColor(GxEPD_BLACK); 433 | u8g2Fonts.print(epd_logo_string); 434 | 435 | y += th - 5; 436 | u8g2Fonts.setForegroundColor(GxEPD_RED); 437 | u8g2Fonts.setFont(u8g2_font_helvR08_tf); 438 | tw = u8g2Fonts.getUTF8Width(epd_logo_copyright); // text box width 439 | u8g2Fonts.setCursor((display.width() - tw) / 2, y); 440 | u8g2Fonts.print(epd_logo_copyright); 441 | display.displayWindow(0, 0, display.width(), display.height()); 442 | 443 | //display.nextPage(); 444 | } 445 | 446 | #define HISTORY_VALUES 100 447 | void epd_update(uint32_t *values, uint32_t pos) 448 | { 449 | char rad[32]; 450 | //u8g2Fonts.setFontMode(1); 451 | //u8g2Fonts.setFontDirection(0); 452 | u8g2Fonts.setForegroundColor(GxEPD_BLACK); 453 | //u8g2Fonts.setBackgroundColor(GxEPD_WHITE); 454 | u8g2Fonts.setFont(u8g2_font_helvR14_tf); 455 | int16_t ta = u8g2Fonts.getFontAscent(); // positive 456 | int16_t td = u8g2Fonts.getFontDescent(); // negative; in mathematicians view 457 | int16_t th = ta - td; // text box height 458 | 459 | uint16_t window_x = 0; 460 | uint16_t window_y = geiger_height + 70; 461 | uint16_t window_width = display.width() - window_x; 462 | uint16_t window_height = display.height() - window_y; 463 | 464 | u8g2Fonts.setCursor(window_x, window_y); 465 | 466 | //display.setPartialWindow(window_x, window_y, window_width, window_height); 467 | display.fillRect(window_x, window_y, window_width, window_height, GxEPD_WHITE); 468 | display.setCursor(window_x, window_y+th); 469 | sprintf(rad, "%2.3f µS/h", values[(pos + HISTORY_VALUES - 1) % HISTORY_VALUES] * 0.006315f); 470 | int16_t tw = u8g2Fonts.getUTF8Width(rad); 471 | u8g2Fonts.setCursor((display.width() - tw) / 2, window_y+th); 472 | u8g2Fonts.print(rad); 473 | 474 | uint32_t plot_height = 40; 475 | uint32_t plot_x = 10; 476 | uint32_t plot_y = window_y + 35 + plot_height; /* baseline! */ 477 | uint32_t plot_width = display.width() - plot_x; 478 | 479 | display.drawLine(plot_x - 1, plot_y - plot_height, plot_x - 1, plot_y + 4, GxEPD_RED); 480 | display.drawLine(plot_x - 4, plot_y + 1, plot_x + plot_width + 4, plot_y + 1, GxEPD_RED); 481 | 482 | uint32_t max_cpm = 100; 483 | 484 | if(pos < HISTORY_VALUES) 485 | { 486 | for(uint32_t entry = 0; entry < MIN(pos, plot_width); entry++) 487 | { 488 | uint32_t y = values[entry % HISTORY_VALUES] * plot_height / max_cpm; 489 | uint16_t color = GxEPD_BLACK; 490 | 491 | if(y > plot_height) 492 | { 493 | y = plot_height; 494 | color = GxEPD_RED; 495 | } 496 | 497 | display.drawLine(plot_x + entry, plot_y - y, plot_x + entry, plot_y, color); 498 | } 499 | } 500 | else 501 | { 502 | for(uint32_t entry = 0; entry < MIN(HISTORY_VALUES, plot_width); entry++) 503 | { 504 | uint32_t y = values[(pos + 1 + entry) % HISTORY_VALUES] * plot_height / max_cpm; 505 | uint16_t color = GxEPD_BLACK; 506 | 507 | if(y > plot_height) 508 | { 509 | y = plot_height; 510 | color = GxEPD_RED; 511 | } 512 | 513 | display.drawLine(plot_x + entry, plot_y - y, plot_x + entry, plot_y, color); 514 | } 515 | } 516 | 517 | display.displayWindow(window_x, window_y, window_width, window_height); 518 | } 519 | 520 | 521 | void epd_loop_task(void * parameter) 522 | { 523 | uint32_t last_values_pos = 0; 524 | uint32_t last_values[HISTORY_VALUES]; 525 | 526 | memset(last_values, 0x00, sizeof(last_values)); 527 | epd_show_logo(); 528 | 529 | while(1) 530 | { 531 | uint32_t now = millis(); 532 | 533 | if(now - epd_lasttime > 60000) 534 | { 535 | epd_lasttime = now; 536 | Serial.printf("[EPD] udpate\n"); 537 | 538 | last_values[last_values_pos++ % HISTORY_VALUES] = det_counts_last; 539 | epd_update(last_values, last_values_pos); 540 | } 541 | 542 | vTaskDelay(1000 / portTICK_PERIOD_MS); 543 | } 544 | } 545 | 546 | void epd_setup() 547 | { 548 | if((current_config.verbose & 16) == 0) 549 | { 550 | Serial.println("[EPD] Disabled, not initializing"); 551 | return; 552 | } 553 | pinMode(EPD_BUSY, INPUT); 554 | pinMode(EPD_RST, OUTPUT); 555 | pinMode(EPD_DC, OUTPUT); 556 | pinMode(EPD_CS, OUTPUT); 557 | 558 | digitalWrite(EPD_CS, HIGH); 559 | 560 | SPI.begin(EPD_SCLK, 13, EPD_MOSI, 15); 561 | 562 | display.init(); 563 | u8g2Fonts.begin(display); 564 | 565 | Serial.println("[EPD] Starting task on core 1"); 566 | xTaskCreate(epd_loop_task, "EPD task", 6000, NULL, 1, NULL); 567 | } 568 | -------------------------------------------------------------------------------- /src/Webserver.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include "Config.h" 5 | #include "Macros.h" 6 | #include "LED.h" 7 | 8 | #define min(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) 9 | #define max(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) 10 | 11 | WebServer webserver(80); 12 | extern char wifi_error[]; 13 | extern bool wifi_captive; 14 | int www_wifi_scanned = -1; 15 | uint32_t www_last_captive = 0; 16 | 17 | void www_setup() 18 | { 19 | webserver.on("/", handle_root); 20 | webserver.on("/index.html", handle_index); 21 | webserver.on("/set_parm", handle_set_parm); 22 | webserver.on("/ota", handle_ota); 23 | webserver.on("/plot", handle_plot); 24 | webserver.on("/voltage", handle_voltage); 25 | webserver.on("/pwm", handle_pwm); 26 | webserver.on("/pwmfreq", handle_pwmfreq); 27 | webserver.on("/counts", handle_counts); 28 | webserver.on("/counts_avg", handle_counts_avg); 29 | webserver.on("/reset", handle_reset); 30 | webserver.on("/test", handle_test); 31 | webserver.on("/play", handle_play); 32 | webserver.onNotFound(handle_404); 33 | 34 | webserver.begin(); 35 | Serial.println("HTTP server started"); 36 | 37 | if (!MDNS.begin(current_config.hostname)) 38 | { 39 | Serial.println("Error setting up MDNS responder!"); 40 | cfg_reset(); 41 | cfg_save(); 42 | ESP.restart(); 43 | } 44 | MDNS.addService("http", "tcp", 80); 45 | MDNS.addService("telnet", "tcp", 23); 46 | } 47 | 48 | unsigned char h2int(char c) 49 | { 50 | if (c >= '0' && c <= '9') 51 | { 52 | return ((unsigned char)c - '0'); 53 | } 54 | if (c >= 'a' && c <= 'f') 55 | { 56 | return ((unsigned char)c - 'a' + 10); 57 | } 58 | if (c >= 'A' && c <= 'F') 59 | { 60 | return ((unsigned char)c - 'A' + 10); 61 | } 62 | return (0); 63 | } 64 | 65 | String urldecode(String str) 66 | { 67 | String encodedString = ""; 68 | char c; 69 | char code0; 70 | char code1; 71 | for (int i = 0; i < str.length(); i++) 72 | { 73 | c = str.charAt(i); 74 | if (c == '+') 75 | { 76 | encodedString += ' '; 77 | } 78 | else if (c == '%') 79 | { 80 | i++; 81 | code0 = str.charAt(i); 82 | i++; 83 | code1 = str.charAt(i); 84 | c = (h2int(code0) << 4) | h2int(code1); 85 | encodedString += c; 86 | } 87 | else 88 | { 89 | encodedString += c; 90 | } 91 | 92 | yield(); 93 | } 94 | 95 | return encodedString; 96 | } 97 | 98 | void www_activity() 99 | { 100 | if (wifi_captive) 101 | { 102 | www_last_captive = millis(); 103 | } 104 | } 105 | 106 | int www_is_captive_active() 107 | { 108 | if (wifi_captive && millis() - www_last_captive < 30000) 109 | { 110 | return 1; 111 | } 112 | return 0; 113 | } 114 | 115 | void handle_404() 116 | { 117 | www_activity(); 118 | 119 | if (wifi_captive) 120 | { 121 | char buf[128]; 122 | sprintf(buf, "HTTP/1.1 302 Found\r\nContent-Type: text/html\r\nContent-length: 0\r\nLocation: http://%s/\r\n\r\n", WiFi.softAPIP().toString().c_str()); 123 | webserver.sendContent(buf); 124 | Serial.printf("[WWW] 302 - http://%s%s/ -> http://%s/\n", webserver.hostHeader().c_str(), webserver.uri().c_str(), WiFi.softAPIP().toString().c_str()); 125 | } 126 | else 127 | { 128 | webserver.send(404, "text/plain", "So empty here"); 129 | Serial.printf("[WWW] 404 - http://%s%s/\n", webserver.hostHeader().c_str(), webserver.uri().c_str()); 130 | } 131 | } 132 | 133 | void handle_index() 134 | { 135 | webserver.send(200, "text/html", SendHTML()); 136 | } 137 | 138 | bool www_loop() 139 | { 140 | webserver.handleClient(); 141 | return false; 142 | } 143 | 144 | void handle_root() 145 | { 146 | webserver.send(200, "text/html", SendHTML()); 147 | } 148 | 149 | void handle_counts() 150 | { 151 | char buf[32]; 152 | sprintf(buf, "%d", det_counts); 153 | webserver.send(200, "text/plain", buf); 154 | } 155 | 156 | void handle_counts_avg() 157 | { 158 | char buf[32]; 159 | sprintf(buf, "%f", det_last_events_avg); 160 | webserver.send(200, "text/plain", buf); 161 | } 162 | 163 | void handle_voltage() 164 | { 165 | char buf[32]; 166 | sprintf(buf, "%f", adc_voltage_avg); 167 | webserver.send(200, "text/plain", buf); 168 | } 169 | 170 | void handle_pwm() 171 | { 172 | char buf[32]; 173 | sprintf(buf, "%f", pwm_value); 174 | webserver.send(200, "text/plain", buf); 175 | } 176 | 177 | void handle_pwmfreq() 178 | { 179 | char buf[32]; 180 | sprintf(buf, "%d", pwm_freq); 181 | webserver.send(200, "text/plain", buf); 182 | } 183 | 184 | void handle_ota() 185 | { 186 | ota_setup(); 187 | webserver.send(200, "text/html", SendHTML()); 188 | } 189 | 190 | void handle_plot() 191 | { 192 | File dataFile = SPIFFS.open("/plot.html", "r"); 193 | webserver.streamFile(dataFile, "text/html"); 194 | dataFile.close(); 195 | } 196 | 197 | void handle_reset() 198 | { 199 | webserver.send(200, "text/html", SendHTML()); 200 | ESP.restart(); 201 | } 202 | 203 | void handle_play() 204 | { 205 | static char tone_buf[1024]; 206 | const char *rtttl = urldecode(webserver.arg("tone")).c_str(); 207 | 208 | if (rtttl != NULL && strlen(rtttl) > 0) 209 | { 210 | strcpy(tone_buf, rtttl); 211 | 212 | for (int pos = 0; pos < strlen(tone_buf); pos++) 213 | { 214 | if (tone_buf[pos] == '-') 215 | { 216 | tone_buf[pos] = '#'; 217 | } 218 | } 219 | webserver.send_P(200, "text/plain", tone_buf); 220 | rtttl_play(tone_buf); 221 | } 222 | else 223 | { 224 | webserver.send(200, "text/plain", "Failed"); 225 | } 226 | } 227 | 228 | void handle_test() 229 | { 230 | uint32_t testmode = max(0, min(1, webserver.arg("testmode").toInt())); 231 | uint32_t freq = max(500, min(500000, webserver.arg("pwm_freq").toInt())); 232 | float duty = max(1, min(99, webserver.arg("pwm_value").toFloat())); 233 | 234 | pwm_testmode(testmode); 235 | 236 | if (testmode) 237 | { 238 | pwm_test(freq, duty); 239 | } 240 | 241 | webserver.send(200, "text/html", "Ok"); 242 | } 243 | 244 | void handle_set_parm() 245 | { 246 | if (webserver.arg("cpu_freq") != "") 247 | { 248 | setCpuFrequencyMhz(max(1, min(240, webserver.arg("cpu_freq").toInt()))); 249 | return; 250 | } 251 | if (webserver.arg("sleep") != "") 252 | { 253 | Serial.printf("[HTTP] sleep mode\n"); 254 | delay(100); 255 | switch (max(1, min(5, webserver.arg("sleep").toInt()))) 256 | { 257 | case 0: 258 | WiFi.disconnect(true); 259 | WiFi.mode(WIFI_OFF); 260 | break; 261 | case 1: 262 | WiFi.setSleep(true); 263 | setCpuFrequencyMhz(40); 264 | break; 265 | case 2: 266 | esp_sleep_enable_timer_wakeup(10 * 1000 * 1000); 267 | esp_light_sleep_start(); 268 | break; 269 | } 270 | return; 271 | } 272 | if (webserver.arg("http_download") != "" && webserver.arg("http_name") != "") 273 | { 274 | String url = webserver.arg("http_download"); 275 | String filename = webserver.arg("http_name"); 276 | HTTPClient http; 277 | 278 | http.begin(url); 279 | 280 | int httpCode = http.GET(); 281 | 282 | Serial.printf("[HTTP] GET... code: %d\n", httpCode); 283 | 284 | switch (httpCode) 285 | { 286 | case HTTP_CODE_OK: 287 | { 288 | int len = http.getSize(); 289 | const int blocksize = 1024; 290 | uint8_t *buffer = (uint8_t *)malloc(blocksize); 291 | 292 | if (!buffer) 293 | { 294 | Serial.printf("[HTTP] Failed to alloc %d byte\n", blocksize); 295 | return; 296 | } 297 | 298 | WiFiClient *stream = http.getStreamPtr(); 299 | File file = SPIFFS.open("/" + filename, "w"); 300 | 301 | if (!file) 302 | { 303 | Serial.printf("[HTTP] Failed to open file\n", blocksize); 304 | return; 305 | } 306 | 307 | int written = 0; 308 | 309 | while (http.connected() && (written < len)) 310 | { 311 | size_t size = stream->available(); 312 | 313 | if (size) 314 | { 315 | int c = stream->readBytes(buffer, ((size > blocksize) ? blocksize : size)); 316 | 317 | if (c > 0) 318 | { 319 | file.write(buffer, c); 320 | written += c; 321 | } 322 | else 323 | { 324 | break; 325 | } 326 | } 327 | } 328 | 329 | free(buffer); 330 | file.close(); 331 | 332 | Serial.printf("[HTTP] Finished. Wrote %d byte to %s\n", written, filename.c_str()); 333 | webserver.send(200, "text/plain", "Downloaded " + url + " and wrote " + written + " byte to " + filename); 334 | break; 335 | } 336 | 337 | default: 338 | { 339 | Serial.print("[HTTP] unexpected response\n"); 340 | webserver.send(200, "text/plain", "Unexpected HTTP status code " + httpCode); 341 | break; 342 | } 343 | } 344 | 345 | return; 346 | } 347 | 348 | if (webserver.arg("http_update") != "") 349 | { 350 | String url = webserver.arg("http_update"); 351 | 352 | Serial.printf("Update from %s\n", url.c_str()); 353 | 354 | led_set_inhibit(false); 355 | 356 | for (int pos = 0; pos < 6; pos++) 357 | { 358 | led_set_adv(pos, 255, 0, 255, pos == 5); 359 | } 360 | 361 | ESPhttpUpdate.rebootOnUpdate(false); 362 | t_httpUpdate_return ret = ESPhttpUpdate.update(url); 363 | 364 | switch (ret) 365 | { 366 | case HTTP_UPDATE_FAILED: 367 | webserver.send(200, "text/plain", "HTTP_UPDATE_FAILED while updating from " + url + " " + ESPhttpUpdate.getLastErrorString()); 368 | Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); 369 | break; 370 | 371 | case HTTP_UPDATE_NO_UPDATES: 372 | webserver.send(200, "text/plain", "HTTP_UPDATE_NO_UPDATES: Updating from " + url); 373 | Serial.println("Update failed: HTTP_UPDATE_NO_UPDATES"); 374 | break; 375 | 376 | case HTTP_UPDATE_OK: 377 | webserver.send(200, "text/html", "

Firmware updated. Rebooting...

(will refresh page in 5 seconds)"); 378 | webserver.close(); 379 | Serial.println("Update successful"); 380 | delay(500); 381 | ESP.restart(); 382 | return; 383 | } 384 | return; 385 | } 386 | 387 | current_config.conv_usv_per_bq = max(0, min(1000, webserver.arg("conv_usv_per_bq").toFloat())); 388 | current_config.pwm_pid_i = max(0, min(1000, webserver.arg("pwm_pid_i").toFloat())); 389 | current_config.pwm_freq = max(1000, min(40000, webserver.arg("pwm_freq").toInt())); 390 | current_config.pwm_freq_min = max(1000, min(50000, webserver.arg("pwm_freq_min").toInt())); 391 | current_config.pwm_freq_max = max(1000, min(50000, webserver.arg("pwm_freq_max").toInt())); 392 | current_config.pwm_value = max(1, min(99, webserver.arg("pwm_value").toFloat())); 393 | current_config.voltage_target = max(1, min(600, webserver.arg("voltage_target").toFloat())); 394 | current_config.voltage_min = max(0, min(420, webserver.arg("voltage_min").toFloat())); 395 | current_config.voltage_max = max(1, min(600, webserver.arg("voltage_max").toFloat())); 396 | current_config.voltage_avg = max(1, min(1024, webserver.arg("voltage_avg").toInt())); 397 | current_config.adc_corr = max(0.1f, min(2.0f, webserver.arg("adc_corr").toFloat())); 398 | current_config.elevated_level = max(1, min(1000, webserver.arg("elevated_level").toInt())); 399 | current_config.buzz_length = max(1, min(1000, webserver.arg("buzz_length").toInt())); 400 | current_config.buzz_freq = max(1, min(20000, webserver.arg("buzz_freq").toInt())); 401 | 402 | current_config.idle_color = strtoul(webserver.arg("idle_color").substring(1).c_str(), NULL, 16); 403 | current_config.elevated_color = strtoul(webserver.arg("elevated_color").substring(1).c_str(), NULL, 16); 404 | current_config.flash_color = strtoul(webserver.arg("flash_color").substring(1).c_str(), NULL, 16); 405 | 406 | current_config.verbose = 0; 407 | current_config.verbose |= (webserver.arg("verbose_c0") != "") ? 1 : 0; 408 | current_config.verbose |= (webserver.arg("verbose_c1") != "") ? 2 : 0; 409 | current_config.verbose |= (webserver.arg("verbose_c2") != "") ? 4 : 0; 410 | current_config.verbose |= (webserver.arg("verbose_c3") != "") ? 8 : 0; 411 | current_config.verbose |= (webserver.arg("verbose_c4") != "") ? 16 : 0; 412 | current_config.mqtt_publish = 0; 413 | current_config.mqtt_publish |= (webserver.arg("mqtt_publish_c0") != "") ? 1 : 0; 414 | current_config.mqtt_publish |= (webserver.arg("mqtt_publish_c1") != "") ? 2 : 0; 415 | current_config.mqtt_publish |= (webserver.arg("mqtt_publish_c2") != "") ? 4 : 0; 416 | current_config.mqtt_publish |= (webserver.arg("mqtt_publish_c3") != "") ? 8 : 0; 417 | 418 | strncpy(current_config.hostname, webserver.arg("hostname").c_str(), sizeof(current_config.hostname)); 419 | strncpy(current_config.wifi_ssid, webserver.arg("wifi_ssid").c_str(), sizeof(current_config.wifi_ssid)); 420 | strncpy(current_config.wifi_password, webserver.arg("wifi_password").c_str(), sizeof(current_config.wifi_password)); 421 | 422 | strncpy(current_config.mqtt_server, webserver.arg("mqtt_server").c_str(), sizeof(current_config.mqtt_server)); 423 | current_config.mqtt_port = max(1, min(65535, webserver.arg("mqtt_port").toInt())); 424 | strncpy(current_config.mqtt_user, webserver.arg("mqtt_user").c_str(), sizeof(current_config.mqtt_user)); 425 | strncpy(current_config.mqtt_password, webserver.arg("mqtt_password").c_str(), sizeof(current_config.mqtt_password)); 426 | strncpy(current_config.mqtt_client, webserver.arg("mqtt_client").c_str(), sizeof(current_config.mqtt_client)); 427 | 428 | cfg_save(); 429 | 430 | if (current_config.verbose) 431 | { 432 | Serial.printf("Config:\n"); 433 | Serial.printf(" pwm_freq: %d Hz\n", current_config.pwm_freq); 434 | Serial.printf(" pwm_start: %2.2f %%\n", current_config.pwm_value); 435 | Serial.printf(" pwm_freq_min: %d Hz\n", current_config.pwm_freq_min); 436 | Serial.printf(" pwm_freq_max: %d Hz\n", current_config.pwm_freq_max); 437 | Serial.printf(" conv_usv_per_bq: %2.2f V\n", current_config.conv_usv_per_bq); 438 | Serial.printf(" voltage_target: %2.2f V\n", current_config.voltage_target); 439 | Serial.printf(" voltage_min: %2.2f V\n", current_config.voltage_min); 440 | Serial.printf(" voltage_max: %2.2f V\n", current_config.voltage_max); 441 | Serial.printf(" voltage_avg: %d samples\n", current_config.voltage_avg); 442 | Serial.printf(" adc_corr: %2.2f\n", current_config.adc_corr); 443 | Serial.printf(" idle_color: #%06X\n", current_config.idle_color); 444 | Serial.printf(" elevated_color: #%06X\n", current_config.elevated_color); 445 | Serial.printf(" elevated_level: %d %%\n", current_config.elevated_level); 446 | Serial.printf(" buzz_length: %d %%\n", current_config.buzz_length); 447 | Serial.printf(" buzz_freq: %d %%\n", current_config.buzz_freq); 448 | Serial.printf(" mqtt_publish: %d %%\n", current_config.mqtt_publish); 449 | Serial.printf(" verbose: %d\n", current_config.verbose); 450 | } 451 | pwm_setup(); 452 | 453 | if (webserver.arg("reboot") == "true") 454 | { 455 | webserver.send(200, "text/html", "

Saved. Rebooting...

(will refresh page in 5 seconds)"); 456 | delay(500); 457 | ESP.restart(); 458 | return; 459 | } 460 | 461 | if (webserver.arg("scan") == "true") 462 | { 463 | www_wifi_scanned = WiFi.scanNetworks(); 464 | } 465 | webserver.send(200, "text/html", SendHTML()); 466 | www_wifi_scanned = -1; 467 | } 468 | 469 | void handle_NotFound() 470 | { 471 | webserver.send(404, "text/plain", "Not found"); 472 | } 473 | 474 | String SendHTML() 475 | { 476 | char buf[1024]; 477 | 478 | www_activity(); 479 | 480 | String ptr = " \n"; 481 | ptr += "\n"; 482 | 483 | sprintf(buf, "" CONFIG_OTANAME " Control\n"); 484 | 485 | ptr += buf; 486 | ptr += "\n"; 510 | /* https://github.com/mdbassit/Coloris */ 511 | ptr += "\n"; 512 | ptr += "\n"; 513 | ptr += "\n"; 514 | ptr += "\n"; 515 | 516 | sprintf(buf, "

" CONFIG_OTANAME "

\n"); 517 | ptr += buf; 518 | 519 | sprintf(buf, "

v1." xstr(PIO_SRC_REVNUM) " - " xstr(PIO_SRC_REV) "

\n"); 520 | ptr += buf; 521 | 522 | if (strlen(wifi_error) != 0) 523 | { 524 | sprintf(buf, "

WiFi Error: %s

\n", wifi_error); 525 | ptr += buf; 526 | } 527 | 528 | if (!ota_enabled()) 529 | { 530 | ptr += "[Enable OTA] "; 531 | } 532 | sprintf(buf, "
Voltage: %3.2f V at %d Hz\n", adc_voltage_avg, pwm_freq); 533 | ptr += buf; 534 | ptr += "

\n"; 535 | 536 | ptr += "
\n"; 537 | ptr += ""; 538 | 539 | #define ADD_CONFIG(name, value, fmt, desc) \ 540 | do \ 541 | { \ 542 | ptr += ""; \ 543 | sprintf(buf, "\n", value); \ 544 | ptr += buf; \ 545 | } while (0) 546 | 547 | #define ADD_CONFIG_CHECK4(name, value, fmt, desc, text0, text1, text2, text3) \ 548 | do \ 549 | { \ 550 | ptr += "\n"); \ 568 | ptr += buf; \ 569 | } while (0) 570 | 571 | #define ADD_CONFIG_CHECK5(name, value, fmt, desc, text0, text1, text2, text3, text4) \ 572 | do \ 573 | { \ 574 | ptr += "\n"); \ 596 | ptr += buf; \ 597 | } while (0) 598 | 599 | #define ADD_CONFIG_RADIO4(name, value, fmt, desc, text0, text1, text2, text3) \ 600 | do \ 601 | { \ 602 | ptr += "\n"); \ 620 | ptr += buf; \ 621 | } while (0) 622 | #define ADD_CONFIG_RADIO2(name, value, fmt, desc, text0, text1) \ 623 | do \ 624 | { \ 625 | ptr += "\n"); \ 635 | ptr += buf; \ 636 | } while (0) 637 | 638 | #define ADD_CONFIG_COLOR(name, value, fmt, desc) \ 639 | do \ 640 | { \ 641 | ptr += ""; \ 642 | sprintf(buf, "\n", value); \ 643 | ptr += buf; \ 644 | } while (0) 645 | 646 | ADD_CONFIG("hostname", current_config.hostname, "%s", "Hostname"); 647 | ADD_CONFIG("wifi_ssid", current_config.wifi_ssid, "%s", "WiFi SSID"); 648 | ADD_CONFIG("wifi_password", current_config.wifi_password, "%s", "WiFi Password"); 649 | 650 | ptr += ""; 681 | 682 | ADD_CONFIG("mqtt_server", current_config.mqtt_server, "%s", "MQTT Server"); 683 | ADD_CONFIG("mqtt_port", current_config.mqtt_port, "%d", "MQTT Port"); 684 | ADD_CONFIG("mqtt_user", current_config.mqtt_user, "%s", "MQTT Username"); 685 | ADD_CONFIG("mqtt_password", current_config.mqtt_password, "%s", "MQTT Password"); 686 | ADD_CONFIG("mqtt_client", current_config.mqtt_client, "%s", "MQTT Client Identification"); 687 | ADD_CONFIG("voltage_target", current_config.voltage_target, "%2.0f", "Voltage target [V]"); 688 | ADD_CONFIG("voltage_min", current_config.voltage_min, "%2.0f", "Voltage minimum [V]"); 689 | ADD_CONFIG("voltage_max", current_config.voltage_max, "%2.0f", "Voltage maximum [V]"); 690 | ADD_CONFIG("voltage_avg", current_config.voltage_avg, "%d", "Voltage averaging [n]"); 691 | ADD_CONFIG("adc_corr", current_config.adc_corr, "%1.2f", "ADC correction"); 692 | ADD_CONFIG("conv_usv_per_bq", current_config.conv_usv_per_bq, "%f", "μSv/h per tick (μSv/h/CPM)"); 693 | ADD_CONFIG("pwm_pid_i", current_config.pwm_pid_i, "%1.2f", "PWM PID I-Gain (Hz/V)"); 694 | ADD_CONFIG("pwm_freq", current_config.pwm_freq, "%d", "PWM frequency startup [Hz]"); 695 | ADD_CONFIG("pwm_freq_min", current_config.pwm_freq_min, "%d", "PWM frequency min [Hz]"); 696 | ADD_CONFIG("pwm_freq_max", current_config.pwm_freq_max, "%d", "PWM frequency max [Hz]"); 697 | ADD_CONFIG("pwm_value", current_config.pwm_value, "%1.2f", "PWM duty cycle [%]"); 698 | ADD_CONFIG("buzz_length", current_config.buzz_length, "%d", "Buzzer duration [ms]"); 699 | ADD_CONFIG("buzz_freq", current_config.buzz_freq, "%d", "Buzzer frequency [Hz]"); 700 | ADD_CONFIG_CHECK5("verbose", current_config.verbose, "%d", "Verbosity", "Serial", "Beep", "Blink", "Fading", "EPD"); 701 | ADD_CONFIG_CHECK4("mqtt_publish", current_config.mqtt_publish, "%d", "MQTT publishes", "Geiger", "Debug", "BME280", "CCS811"); 702 | ADD_CONFIG_COLOR("idle_color", current_config.idle_color, "#%06X", "Idle color"); 703 | ADD_CONFIG_COLOR("elevated_color", current_config.elevated_color, "#%06X", "Elevated color"); 704 | ADD_CONFIG_COLOR("flash_color", current_config.flash_color, "#%06X", "Flash color"); 705 | ADD_CONFIG("elevated_level", current_config.elevated_level, "%d", "Elevated level [cpm]"); 706 | ADD_CONFIG("http_update", "", "%s", "Update URL (Release)"); 707 | 708 | ptr += "
" desc ":
"; \ 551 | sprintf(buf, "\n", (value & 1) ? "checked" : ""); \ 552 | ptr += buf; \ 553 | sprintf(buf, "\n"); \ 554 | ptr += buf; \ 555 | sprintf(buf, "\n", (value & 2) ? "checked" : ""); \ 556 | ptr += buf; \ 557 | sprintf(buf, "\n"); \ 558 | ptr += buf; \ 559 | sprintf(buf, "\n", (value & 4) ? "checked" : ""); \ 560 | ptr += buf; \ 561 | sprintf(buf, "\n"); \ 562 | ptr += buf; \ 563 | sprintf(buf, "\n", (value & 8) ? "checked" : ""); \ 564 | ptr += buf; \ 565 | sprintf(buf, "\n"); \ 566 | ptr += buf; \ 567 | sprintf(buf, "
" desc ":
"; \ 575 | sprintf(buf, "\n", (value & 1) ? "checked" : ""); \ 576 | ptr += buf; \ 577 | sprintf(buf, "\n"); \ 578 | ptr += buf; \ 579 | sprintf(buf, "\n", (value & 2) ? "checked" : ""); \ 580 | ptr += buf; \ 581 | sprintf(buf, "\n"); \ 582 | ptr += buf; \ 583 | sprintf(buf, "\n", (value & 4) ? "checked" : ""); \ 584 | ptr += buf; \ 585 | sprintf(buf, "\n"); \ 586 | ptr += buf; \ 587 | sprintf(buf, "\n", (value & 8) ? "checked" : ""); \ 588 | ptr += buf; \ 589 | sprintf(buf, "\n"); \ 590 | ptr += buf; \ 591 | sprintf(buf, "\n", (value & 16) ? "checked" : ""); \ 592 | ptr += buf; \ 593 | sprintf(buf, "\n"); \ 594 | ptr += buf; \ 595 | sprintf(buf, "
" desc ":
"; \ 603 | sprintf(buf, "\n", (value == 0) ? "checked" : ""); \ 604 | ptr += buf; \ 605 | sprintf(buf, "\n"); \ 606 | ptr += buf; \ 607 | sprintf(buf, "\n", (value == 1) ? "checked" : ""); \ 608 | ptr += buf; \ 609 | sprintf(buf, "\n"); \ 610 | ptr += buf; \ 611 | sprintf(buf, "\n", (value == 2) ? "checked" : ""); \ 612 | ptr += buf; \ 613 | sprintf(buf, "\n"); \ 614 | ptr += buf; \ 615 | sprintf(buf, "\n", (value == 3) ? "checked" : ""); \ 616 | ptr += buf; \ 617 | sprintf(buf, "\n"); \ 618 | ptr += buf; \ 619 | sprintf(buf, "
" desc ":
"; \ 626 | sprintf(buf, "\n", (value == 0) ? "checked" : ""); \ 627 | ptr += buf; \ 628 | sprintf(buf, "\n"); \ 629 | ptr += buf; \ 630 | sprintf(buf, "\n", (value == 1) ? "checked" : ""); \ 631 | ptr += buf; \ 632 | sprintf(buf, "\n"); \ 633 | ptr += buf; \ 634 | sprintf(buf, "
WiFi networks:"; 651 | 652 | if (www_wifi_scanned == -1) 653 | { 654 | ptr += ""; 655 | } 656 | else if (www_wifi_scanned == 0) 657 | { 658 | ptr += "No networks found, "; 659 | } 660 | else 661 | { 662 | ptr += ""; 663 | ptr += ""; 664 | for (int i = 0; i < www_wifi_scanned; ++i) 665 | { 666 | if (WiFi.SSID(i) != "") 667 | { 668 | ptr += ""; 675 | } 676 | } 677 | ptr += "
"; 671 | ptr += WiFi.SSID(i); 672 | ptr += ""; 673 | ptr += WiFi.RSSI(i); 674 | ptr += " dBm
"; 678 | } 679 | 680 | ptr += "
\n"; 709 | ptr += "\n"; 710 | ptr += "\n"; 711 | 712 | return ptr; 713 | } 714 | --------------------------------------------------------------------------------