├── .gitignore ├── .vscode └── extensions.json ├── include ├── Config.h └── README ├── platformio.ini ├── src ├── LED.ino ├── Config.ino ├── OTA.ino ├── AntennaSwitch.ino ├── MQTT.ino ├── WiFi.ino ├── Time.ino └── Webserver.ino ├── lib └── README └── 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 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /include/Config.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H__ 2 | #define __CONFIG_H__ 3 | 4 | #define CONFIG_MAGIC 0xE1AAFF00 5 | typedef struct 6 | { 7 | uint32_t magic; 8 | char hostname[32]; 9 | uint32_t verbose; 10 | uint8_t rf1_cfg; 11 | uint8_t rf2_cfg; 12 | uint8_t rf3_cfg; 13 | uint8_t rf4_cfg; 14 | uint8_t rfo1_amp; 15 | uint8_t rfo2_amp; 16 | } t_cfg; 17 | 18 | 19 | extern t_cfg current_config; 20 | 21 | 22 | #endif -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32dev] 12 | platform = espressif32 13 | board = esp32dev 14 | framework = arduino 15 | board_build.f_cpu = 240000000L 16 | monitor_speed = 115200 17 | lib_deps = 18 | fastled/FastLED@^3.5.0 19 | knolleary/PubSubClient@^2.8 20 | ;upload_protocol = espota 21 | ;upload_port = Geiger-v3 -------------------------------------------------------------------------------- /src/LED.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define LED_GPIO 27 4 | #define LED_COUNT 1 5 | 6 | CRGB leds[LED_COUNT]; 7 | 8 | void led_setup() 9 | { 10 | FastLED.addLeds(leds, LED_COUNT); 11 | for(int num = 0; num < LED_COUNT; num++) 12 | { 13 | led_set_adv(num, 0, 0, 0, false); 14 | } 15 | led_set_adv(0, 0, 0, 16, true); 16 | } 17 | 18 | void led_set_adv(uint8_t n, uint8_t r, uint8_t g, uint8_t b, bool commit) 19 | { 20 | leds[n] = CRGB(r, g, b); 21 | 22 | if (commit) 23 | { 24 | FastLED.show(); 25 | } 26 | } 27 | void led_set(uint8_t n, uint8_t r, uint8_t g, uint8_t b) 28 | { 29 | return led_set_adv(n, r, g, b, true); 30 | } 31 | 32 | bool led_loop() 33 | { 34 | return false; 35 | } 36 | -------------------------------------------------------------------------------- /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/Config.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "Config.h" 6 | 7 | t_cfg current_config; 8 | 9 | void cfg_save() 10 | { 11 | File file = SPIFFS.open("/config.dat", "w"); 12 | if (!file || file.isDirectory()) 13 | { 14 | Serial.printf("[cfg] Failed to write\n"); 15 | return; 16 | } 17 | 18 | if (strlen(current_config.hostname) < 2) 19 | { 20 | strcpy(current_config.hostname, "AntennaSwitch"); 21 | } 22 | 23 | file.write((uint8_t *)¤t_config, sizeof(current_config)); 24 | file.close(); 25 | Serial.printf("[cfg] Written config\n"); 26 | } 27 | 28 | void cfg_reset() 29 | { 30 | memset(¤t_config, 0x00, sizeof(current_config)); 31 | 32 | current_config.magic = CONFIG_MAGIC; 33 | strcpy(current_config.hostname, "AntennaSwitch"); 34 | 35 | current_config.rf1_cfg = 0; 36 | current_config.rf2_cfg = 0; 37 | current_config.rf3_cfg = 0; 38 | current_config.rf4_cfg = 0; 39 | current_config.rfo1_amp = 0; 40 | current_config.rfo2_amp = 0; 41 | current_config.verbose = 7; 42 | 43 | cfg_save(); 44 | } 45 | 46 | void cfg_read() 47 | { 48 | File file = SPIFFS.open("/config.dat", "r"); 49 | 50 | if (!file || file.isDirectory()) 51 | { 52 | Serial.printf("[cfg] Failed to read, resetting\n"); 53 | cfg_reset(); 54 | } 55 | else 56 | { 57 | file.read((uint8_t *)¤t_config, sizeof(current_config)); 58 | file.close(); 59 | 60 | if (current_config.magic != CONFIG_MAGIC) 61 | { 62 | Serial.printf("[cfg] Invalid magic, resetting\n"); 63 | cfg_reset(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/OTA.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | bool ota_active = false; 5 | bool ota_setup_done = false; 6 | long ota_offtime = 0; 7 | 8 | void ota_setup() 9 | { 10 | if(ota_setup_done) 11 | { 12 | ota_enable(); 13 | return; 14 | } 15 | ArduinoOTA.setHostname("AntennaSwitch"); 16 | 17 | ArduinoOTA.onStart([]() { 18 | String type; 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 | -------------------------------------------------------------------------------- /data/plot.html: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 24 | 25 | 26 |

Geiger v3

27 |
28 |
29 | 30 | 110 | 111 | -------------------------------------------------------------------------------- /src/AntennaSwitch.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Config.h" 10 | 11 | 12 | int led_r = 0; 13 | int led_g = 0; 14 | int led_b = 0; 15 | uint8_t switches[] = { 4, 18, 19, 21, 22, 23, 32, 33, 25, 13, 26, 14 }; 16 | 17 | #define SW1 0 18 | #define SW2 1 19 | #define SW3 2 20 | #define SW4 3 21 | #define SW5 4 22 | #define SW6 5 23 | #define SW7 6 24 | #define SW8 7 25 | #define SW9 8 26 | #define SW10 9 27 | #define SW11 10 28 | #define SW12 11 29 | 30 | 31 | #define SW1_1 SW8 32 | #define SW1_2 SW7 33 | #define SW2_1 SW6 34 | #define SW2_2 SW5 35 | #define SW3_1 SW4 36 | #define SW3_2 SW3 37 | #define SW4_1 SW1 38 | #define SW4_2 SW2 39 | #define SW5_1 SW11 40 | #define SW5_2 SW10 41 | #define SW6_1 SW12 42 | #define SW6_2 SW9 43 | 44 | void setup() 45 | { 46 | Serial.begin(115200); 47 | Serial.printf("\n\n\n"); 48 | 49 | Serial.printf("[i] SDK: '%s'\n", ESP.getSdkVersion()); 50 | Serial.printf("[i] CPU Speed: %d MHz\n", ESP.getCpuFreqMHz()); 51 | Serial.printf("[i] Chip Id: %06llX\n", ESP.getEfuseMac()); 52 | Serial.printf("[i] Flash Mode: %08X\n", ESP.getFlashChipMode()); 53 | Serial.printf("[i] Flash Size: %08X\n", ESP.getFlashChipSize()); 54 | Serial.printf("[i] Flash Speed: %d MHz\n", ESP.getFlashChipSpeed() / 1000000); 55 | Serial.printf("[i] Heap %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize()); 56 | Serial.printf("[i] SPIRam %d/%d\n", ESP.getFreePsram(), ESP.getPsramSize()); 57 | Serial.printf("\n"); 58 | Serial.printf("[i] Starting\n"); 59 | 60 | Serial.printf("[i] Setup LEDs\n"); 61 | led_setup(); 62 | Serial.printf("[i] Setup SPIFFS\n"); 63 | if (!SPIFFS.begin(true)) 64 | { 65 | Serial.println("[E] SPIFFS Mount Failed"); 66 | } 67 | cfg_read(); 68 | Serial.printf("[i] Setup WiFi\n"); 69 | wifi_setup(); 70 | Serial.printf("[i] Setup Webserver\n"); 71 | www_setup(); 72 | Serial.printf("[i] Setup Time\n"); 73 | time_setup(); 74 | Serial.printf("[i] Setup MQTT\n"); 75 | mqtt_setup(); 76 | 77 | for(int sw = 0; sw < sizeof(switches); sw++) 78 | { 79 | pinMode(switches[sw], OUTPUT); 80 | digitalWrite(switches[sw], LOW); 81 | } 82 | 83 | Serial.println("Setup done"); 84 | } 85 | 86 | void mux_write(int pin1, int pin2, int mode) 87 | { 88 | digitalWrite(switches[pin1], (mode & 2) ? HIGH : LOW); 89 | digitalWrite(switches[pin2], (mode & 1) ? HIGH : LOW); 90 | } 91 | 92 | void amp_write(int pin1, int pin2, int enabled) 93 | { 94 | digitalWrite(switches[pin1], enabled ? LOW : HIGH); 95 | digitalWrite(switches[pin2], enabled ? HIGH : LOW); 96 | } 97 | 98 | void loop() 99 | { 100 | bool hasWork = false; 101 | 102 | hasWork |= led_loop(); 103 | hasWork |= wifi_loop(); 104 | hasWork |= www_loop(); 105 | hasWork |= time_loop(); 106 | hasWork |= mqtt_loop(); 107 | hasWork |= ota_loop(); 108 | 109 | mux_write(SW1_1, SW1_2, current_config.rf1_cfg); 110 | mux_write(SW2_1, SW2_2, current_config.rf2_cfg); 111 | mux_write(SW3_1, SW3_2, current_config.rf3_cfg); 112 | mux_write(SW4_1, SW4_2, current_config.rf4_cfg); 113 | amp_write(SW5_1, SW5_2, current_config.rfo1_amp); 114 | amp_write(SW6_1, SW6_2, current_config.rfo2_amp); 115 | } 116 | -------------------------------------------------------------------------------- /src/MQTT.ino: -------------------------------------------------------------------------------- 1 | #define MQTT_DEBUG 2 | 3 | #include 4 | 5 | #define ARB_SERVER "xxx" 6 | #define ARB_CLIENT "AntennaSwitch" 7 | #define ARB_SERVERPORT 11883 8 | #define ARB_USERNAME "xxx" 9 | #define ARB_PW "xxx" 10 | 11 | WiFiClient client; 12 | PubSubClient mqtt(client); 13 | 14 | extern uint32_t lon_rx_count; 15 | extern uint32_t lon_crc_errors; 16 | 17 | int mqtt_last_publish_time = 0; 18 | int mqtt_lastConnect = 0; 19 | int mqtt_retries = 0; 20 | bool mqtt_fail = false; 21 | 22 | void callback(char *topic, byte *payload, unsigned int length) 23 | { 24 | Serial.print("Message arrived ["); 25 | Serial.print(topic); 26 | Serial.print("] "); 27 | for (int i = 0; i < length; i++) 28 | { 29 | Serial.print((char)payload[i]); 30 | } 31 | Serial.println(); 32 | } 33 | 34 | void mqtt_setup() 35 | { 36 | mqtt.setServer(ARB_SERVER, ARB_SERVERPORT); 37 | mqtt.setCallback(callback); 38 | } 39 | 40 | void mqtt_publish_float(char *name, float value) 41 | { 42 | char buffer[32]; 43 | 44 | sprintf(buffer, "%0.2f", value); 45 | if (!mqtt.publish(name, buffer)) 46 | { 47 | mqtt_fail = true; 48 | } 49 | Serial.printf("Published %s : %s\n", name, buffer); 50 | } 51 | 52 | void mqtt_publish_int(char *name, uint32_t value) 53 | { 54 | char buffer[32]; 55 | 56 | if (value == 0x7FFFFFFF) 57 | { 58 | return; 59 | } 60 | sprintf(buffer, "%d", value); 61 | mqtt.publish(name, buffer); 62 | Serial.printf("Published %s : %s\n", name, buffer); 63 | } 64 | 65 | bool mqtt_loop() 66 | { 67 | uint32_t time = millis(); 68 | static int nextTime = 0; 69 | 70 | if (mqtt_fail) 71 | { 72 | mqtt_fail = false; 73 | mqtt.disconnect(); 74 | } 75 | 76 | MQTT_connect(); 77 | 78 | if (!mqtt.connected()) 79 | { 80 | return false; 81 | } 82 | 83 | mqtt.loop(); 84 | 85 | if (time >= nextTime) 86 | { 87 | bool do_publish = false; 88 | 89 | if ((time - mqtt_last_publish_time) > 60000) 90 | { 91 | do_publish = true; 92 | } 93 | 94 | if (do_publish) 95 | { 96 | mqtt_last_publish_time = time; 97 | //mqtt_publish_int((char*)"feeds/integer/geiger/ticks", counts); 98 | //mqtt_publish_float((char*)"feeds/float/geiger/voltage", adc_voltage_avg); 99 | } 100 | nextTime = time + 1000; 101 | } 102 | 103 | return false; 104 | } 105 | 106 | void MQTT_connect() 107 | { 108 | int curTime = millis(); 109 | int8_t ret; 110 | 111 | if (WiFi.status() != WL_CONNECTED) 112 | { 113 | return; 114 | } 115 | 116 | if (mqtt.connected()) 117 | { 118 | return; 119 | } 120 | 121 | if ((mqtt_lastConnect != 0) && (curTime - mqtt_lastConnect < (1000 << mqtt_retries))) 122 | { 123 | return; 124 | } 125 | 126 | mqtt_lastConnect = curTime; 127 | 128 | Serial.print("MQTT: Connecting to MQTT... "); 129 | ret = mqtt.connect(ARB_CLIENT, ARB_USERNAME, ARB_PW); 130 | 131 | if (ret == 0) 132 | { 133 | mqtt_retries++; 134 | if (mqtt_retries > 8) 135 | { 136 | mqtt_retries = 8; 137 | } 138 | Serial.printf("MQTT: (%d) ", mqtt.state()); 139 | Serial.println("MQTT: Retrying MQTT connection"); 140 | mqtt.disconnect(); 141 | } 142 | else 143 | { 144 | Serial.println("MQTT Connected!"); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/WiFi.ino: -------------------------------------------------------------------------------- 1 | 2 | const char *ssid = "xxx"; 3 | const char *password = "xxx"; 4 | bool connecting = false; 5 | 6 | void wifi_setup() 7 | { 8 | Serial.printf("[WiFi] Connecting...\n"); 9 | WiFi.begin(ssid, password); 10 | connecting = true; 11 | led_set(0, 8, 8, 0); 12 | } 13 | 14 | void wifi_off() 15 | { 16 | connecting = false; 17 | WiFi.disconnect(); 18 | WiFi.mode(WIFI_OFF); 19 | } 20 | 21 | bool wifi_loop(void) 22 | { 23 | int status = WiFi.status(); 24 | int curTime = millis(); 25 | static int nextTime = 0; 26 | static int stateCounter = 0; 27 | 28 | if (nextTime > curTime) 29 | { 30 | return false; 31 | } 32 | 33 | /* standard refresh time */ 34 | nextTime = curTime + 500; 35 | 36 | /* when stuck at a state, disconnect */ 37 | if (++stateCounter > 40) 38 | { 39 | Serial.printf("[WiFi] Timeout, aborting\n"); 40 | led_set(0, 255, 0, 255); 41 | wifi_off(); 42 | stateCounter = 0; 43 | return false; 44 | } 45 | 46 | switch (status) 47 | { 48 | case WL_CONNECTED: 49 | if (connecting) 50 | { 51 | led_set(0, 0, 4, 0); 52 | connecting = false; 53 | Serial.print("[WiFi] Connected, IP address: "); 54 | Serial.println(WiFi.localIP()); 55 | stateCounter = 0; 56 | } 57 | else 58 | { 59 | static int last_rssi = -1; 60 | int rssi = WiFi.RSSI(); 61 | 62 | if (last_rssi != rssi) 63 | { 64 | float maxRssi = -40; 65 | float minRssi = -90; 66 | float strRatio = (rssi - minRssi) / (maxRssi - minRssi); 67 | float strength = min(1, max(0, strRatio)); 68 | float brightness = 0.08f; 69 | int r = brightness * 255.0f * (1.0f - strength); 70 | int g = brightness * 255.0f * strength; 71 | 72 | led_set(0, r, g, 0); 73 | //Serial.printf("[WiFi] RSSI %d, strength: %1.2f, r: %d, g: %d\n", rssi, strength, r, g); 74 | 75 | last_rssi = rssi; 76 | } 77 | 78 | /* happy with this state, reset counter */ 79 | stateCounter = 0; 80 | } 81 | break; 82 | 83 | case WL_CONNECTION_LOST: 84 | Serial.printf("[WiFi] Connection lost\n"); 85 | led_set(0, 32, 8, 0); 86 | wifi_off(); 87 | break; 88 | 89 | case WL_CONNECT_FAILED: 90 | Serial.printf("[WiFi] Connection failed\n"); 91 | led_set(0, 255, 0, 0); 92 | wifi_off(); 93 | break; 94 | 95 | case WL_NO_SSID_AVAIL: 96 | Serial.printf("[WiFi] No SSID\n"); 97 | led_set(0, 32, 0, 32); 98 | wifi_off(); 99 | break; 100 | 101 | case WL_SCAN_COMPLETED: 102 | Serial.printf("[WiFi] Scan completed\n"); 103 | wifi_off(); 104 | break; 105 | 106 | case WL_DISCONNECTED: 107 | if (!connecting) 108 | { 109 | Serial.printf("[WiFi] Disconnected\n"); 110 | led_set(0, 255, 0, 255); 111 | wifi_off(); 112 | } 113 | break; 114 | 115 | case WL_IDLE_STATUS: 116 | if (!connecting) 117 | { 118 | connecting = true; 119 | Serial.printf("[WiFi] Idle, connect to %s\n", ssid); 120 | WiFi.mode(WIFI_STA); 121 | WiFi.begin(ssid, password); 122 | } 123 | else 124 | { 125 | Serial.printf("[WiFi] Idle, connecting...\n"); 126 | } 127 | break; 128 | 129 | case WL_NO_SHIELD: 130 | if (!connecting) 131 | { 132 | connecting = true; 133 | Serial.printf("[WiFi] Disabled (%d), connecting to %s\n", status, ssid); 134 | WiFi.mode(WIFI_STA); 135 | WiFi.begin(ssid, password); 136 | } 137 | break; 138 | 139 | default: 140 | Serial.printf("[WiFi] unknown (%d), disable\n", status); 141 | wifi_off(); 142 | break; 143 | } 144 | 145 | return false; 146 | } 147 | -------------------------------------------------------------------------------- /src/Time.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | #include "Time.h" 5 | 6 | enum statusType 7 | { 8 | Idle, 9 | Sent, 10 | Received, 11 | Pause 12 | }; 13 | 14 | IPAddress timeServerIP; // time.nist.gov NTP server address 15 | const char* ntpServerName = "time.nist.gov"; 16 | unsigned int localPort = 2390; // local port to listen for UDP packets 17 | const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message 18 | byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets 19 | WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP 20 | 21 | uint32_t retries = 0; 22 | unsigned long lastSent = 0; 23 | unsigned long timeReference = 0; 24 | bool time_valid = false; 25 | unsigned long secsSince1900 = 0; 26 | unsigned long setup_time_offset = 2; 27 | 28 | 29 | /* 2000-03-01 (mod 400 year, immediately after feb29 */ 30 | #define LEAPOCH (946684800LL + 86400*(31+29)) 31 | #define DAYS_PER_400Y (365*400 + 97) 32 | #define DAYS_PER_100Y (365*100 + 24) 33 | #define DAYS_PER_4Y (365*4 + 1) 34 | 35 | statusType currentStatus = Idle; 36 | 37 | void time_setup() 38 | { 39 | Udp.begin(localPort); 40 | currentStatus = Idle; 41 | lastSent = 0; 42 | timeReference = 0; 43 | memset(packetBuffer, 0x00, sizeof(packetBuffer)); 44 | } 45 | 46 | const char *Time_getStateString() 47 | { 48 | static char retString[64]; 49 | const char *state = ""; 50 | 51 | switch(currentStatus) 52 | { 53 | default: 54 | state = "Unknown state"; 55 | break; 56 | 57 | case Idle: 58 | state = "Idle"; 59 | break; 60 | 61 | case Sent: 62 | state = "Sent"; 63 | break; 64 | 65 | case Received: 66 | state = "Received"; 67 | break; 68 | 69 | case Pause: 70 | state = "Pause"; 71 | break; 72 | } 73 | 74 | snprintf(retString, sizeof(retString), "%s, ref: %lu, last: %lu, retries: %u, millis(): %lu", state, timeReference, lastSent, retries, millis()); 75 | 76 | return retString; 77 | } 78 | 79 | bool time_loop() 80 | { 81 | if(WiFi.status() != WL_CONNECTED) 82 | { 83 | return false; 84 | } 85 | 86 | switch(currentStatus) 87 | { 88 | default: 89 | Serial.println("[NTP] Unknown state"); 90 | currentStatus = Idle; 91 | break; 92 | 93 | case Idle: 94 | if(!time_valid || millis() - lastSent > 1000 * 60 * 60) 95 | { 96 | Serial.println("[NTP] Sending request"); 97 | 98 | lastSent = millis(); 99 | currentStatus = Sent; 100 | WiFi.hostByName(ntpServerName, timeServerIP); 101 | sendNTPpacket(timeServerIP); // send an NTP packet to a time server 102 | } 103 | break; 104 | 105 | case Sent: 106 | if(millis() - lastSent > 1000 * 10) 107 | { 108 | Serial.println("[NTP] No reply, resend"); 109 | if(retries < 10) 110 | { 111 | retries++; 112 | currentStatus = Idle; 113 | } 114 | else 115 | { 116 | currentStatus = Pause; 117 | } 118 | } 119 | else if(Udp.parsePacket()) 120 | { 121 | timeReference = millis(); 122 | currentStatus = Received; 123 | Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer 124 | } 125 | break; 126 | 127 | case Received: 128 | { 129 | unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); 130 | unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); 131 | secsSince1900 = highWord << 16 | lowWord; 132 | 133 | printTime(); 134 | 135 | if(!time_valid) 136 | { 137 | time_valid = true; 138 | } 139 | 140 | retries = 0; 141 | currentStatus = Idle; 142 | break; 143 | } 144 | 145 | case Pause: 146 | if(millis() - lastSent > 1000 * 60 * 2) 147 | { 148 | currentStatus = Idle; 149 | } 150 | break; 151 | } 152 | 153 | return false; 154 | } 155 | 156 | void printTime() 157 | { 158 | struct tm tm; 159 | getTime(&tm); 160 | 161 | Serial.printf("[NTP] The time is: %02d:%02d:%02d\n", tm.tm_hour, tm.tm_min, tm.tm_sec); 162 | } 163 | 164 | int secs_to_tm(long long t, struct tm *tm) 165 | { 166 | long long days, secs; 167 | int remdays, remsecs, remyears; 168 | int qc_cycles, c_cycles, q_cycles; 169 | int years, months; 170 | int wday, yday, leap; 171 | static const char days_in_month[] = {31,30,31,30,31,31,30,31,30,31,31,29}; 172 | 173 | secs = t - LEAPOCH; 174 | days = secs / 86400; 175 | remsecs = secs % 86400; 176 | if (remsecs < 0) { 177 | remsecs += 86400; 178 | days--; 179 | } 180 | 181 | wday = (3+days)%7; 182 | if (wday < 0) wday += 7; 183 | 184 | qc_cycles = days / DAYS_PER_400Y; 185 | remdays = days % DAYS_PER_400Y; 186 | if (remdays < 0) { 187 | remdays += DAYS_PER_400Y; 188 | qc_cycles--; 189 | } 190 | 191 | c_cycles = remdays / DAYS_PER_100Y; 192 | if (c_cycles == 4) c_cycles--; 193 | remdays -= c_cycles * DAYS_PER_100Y; 194 | 195 | q_cycles = remdays / DAYS_PER_4Y; 196 | if (q_cycles == 25) q_cycles--; 197 | remdays -= q_cycles * DAYS_PER_4Y; 198 | 199 | remyears = remdays / 365; 200 | if (remyears == 4) remyears--; 201 | remdays -= remyears * 365; 202 | 203 | leap = !remyears && (q_cycles || !c_cycles); 204 | yday = remdays + 31 + 28 + leap; 205 | if (yday >= 365+leap) yday -= 365+leap; 206 | 207 | years = remyears + 4*q_cycles + 100*c_cycles + 400*qc_cycles; 208 | 209 | for (months=0; days_in_month[months] <= remdays; months++) 210 | remdays -= days_in_month[months]; 211 | 212 | tm->tm_year = years + 100; 213 | tm->tm_mon = months + 2; 214 | if (tm->tm_mon >= 12) { 215 | tm->tm_mon -=12; 216 | tm->tm_year++; 217 | } 218 | tm->tm_mday = remdays; 219 | tm->tm_wday = wday; 220 | tm->tm_yday = yday; 221 | 222 | tm->tm_hour = remsecs / 3600; 223 | tm->tm_min = remsecs / 60 % 60; 224 | tm->tm_sec = remsecs % 60; 225 | 226 | return 0; 227 | } 228 | 229 | void getTimeAdv(struct tm* tm, unsigned long offset) 230 | { 231 | unsigned long epoch = secsSince1900 - 2208988800UL; 232 | 233 | long secs = ((long)offset - (long)timeReference) / 1000; 234 | epoch += 60 * 60 * setup_time_offset; 235 | epoch += secs; 236 | 237 | secs_to_tm(epoch, tm); 238 | } 239 | 240 | void getTime(struct tm* tm) 241 | { 242 | getTimeAdv(tm, millis()); 243 | } 244 | 245 | void getStartupTime(struct tm* tm) 246 | { 247 | getTimeAdv(tm, 0); 248 | } 249 | 250 | // send an NTP request to the time server at the given address 251 | void sendNTPpacket(IPAddress& address) 252 | { 253 | memset(packetBuffer, 0, NTP_PACKET_SIZE); 254 | 255 | // Initialize values needed to form NTP request 256 | packetBuffer[0] = 0b11100011; // LI, Version, Mode 257 | packetBuffer[1] = 0; // Stratum, or type of clock 258 | packetBuffer[2] = 6; // Polling Interval 259 | packetBuffer[3] = 0xEC; // Peer Clock Precision 260 | // 8 bytes of zero for Root Delay & Root Dispersion 261 | packetBuffer[12] = 49; 262 | packetBuffer[13] = 0x4E; 263 | packetBuffer[14] = 49; 264 | packetBuffer[15] = 52; 265 | 266 | Udp.beginPacket(address, 123); //NTP requests are to port 123 267 | Udp.write(packetBuffer, NTP_PACKET_SIZE); 268 | Udp.endPacket(); 269 | } 270 | 271 | 272 | -------------------------------------------------------------------------------- /src/Webserver.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "Config.h" 4 | 5 | WebServer webserver(80); 6 | 7 | #define min(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) 8 | #define max(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) 9 | 10 | void www_setup() 11 | { 12 | webserver.on("/", handle_OnConnect); 13 | webserver.on("/set_parm", handle_set_parm); 14 | webserver.on("/ota", handle_ota); 15 | webserver.on("/reset", handle_reset); 16 | webserver.onNotFound(handle_NotFound); 17 | 18 | webserver.begin(); 19 | Serial.println("HTTP server started"); 20 | 21 | if (!MDNS.begin(current_config.hostname)) 22 | { 23 | Serial.println("Error setting up MDNS responder!"); 24 | while (1) 25 | { 26 | delay(1000); 27 | } 28 | } 29 | MDNS.addService("http", "tcp", 80); 30 | } 31 | 32 | bool www_loop() 33 | { 34 | webserver.handleClient(); 35 | return false; 36 | } 37 | 38 | void handle_OnConnect() 39 | { 40 | webserver.send(200, "text/html", SendHTML()); 41 | } 42 | 43 | 44 | void handle_ota() 45 | { 46 | ota_setup(); 47 | webserver.send(200, "text/html", SendHTML()); 48 | } 49 | 50 | void handle_plot() 51 | { 52 | File dataFile = SPIFFS.open("/plot.html", "r"); 53 | webserver.streamFile(dataFile, "text/html"); 54 | dataFile.close(); 55 | } 56 | 57 | void handle_reset() 58 | { 59 | webserver.send(200, "text/html", SendHTML()); 60 | ESP.restart(); 61 | } 62 | 63 | void handle_set_parm() 64 | { 65 | current_config.rf1_cfg = max(0, min(3, webserver.arg("rf1_cfg").toInt())); 66 | current_config.rf2_cfg = max(0, min(3, webserver.arg("rf2_cfg").toInt())); 67 | current_config.rf3_cfg = max(0, min(3, webserver.arg("rf3_cfg").toInt())); 68 | current_config.rf4_cfg = max(0, min(3, webserver.arg("rf4_cfg").toInt())); 69 | current_config.rfo1_amp = max(0, min(1, webserver.arg("rfo1_amp").toInt())); 70 | current_config.rfo2_amp = max(0, min(1, webserver.arg("rfo2_amp").toInt())); 71 | current_config.verbose = max(1, min(99, webserver.arg("verbose").toInt())); 72 | 73 | strncpy(current_config.hostname, webserver.arg("hostname").c_str(), sizeof(current_config.hostname)); 74 | 75 | cfg_save(); 76 | 77 | if (current_config.verbose) 78 | { 79 | Serial.printf("Config:\n"); 80 | Serial.printf(" verbose: %d\n", current_config.verbose); 81 | } 82 | webserver.send(200, "text/html", SendHTML()); 83 | } 84 | 85 | void handle_NotFound() 86 | { 87 | webserver.send(404, "text/plain", "Not found"); 88 | } 89 | 90 | String SendHTML() 91 | { 92 | char buf[1024]; 93 | 94 | String ptr = " \n"; 95 | ptr += "\n"; 96 | 97 | sprintf(buf, "AntennaSwitch Control\n"); 98 | 99 | ptr += buf; 100 | ptr += "\n"; 119 | ptr += "\n"; 120 | ptr += "\n"; 121 | 122 | sprintf(buf, "

AntennaSwitch

\n"); 123 | 124 | ptr += buf; 125 | if (!ota_enabled()) 126 | { 127 | ptr += "[Enable OTA] "; 128 | } 129 | ptr += buf; 130 | ptr += "

\n"; 131 | 132 | ptr += "
\n"; 133 | ptr += ""; 134 | 135 | #define ADD_CONFIG(name, value, fmt, desc) \ 136 | do \ 137 | { \ 138 | ptr += ""; \ 139 | sprintf(buf, "\n", value); \ 140 | ptr += buf; \ 141 | } while (0) 142 | 143 | #define ADD_CONFIG_RADIO4(name, value, fmt, desc, text0, text1, text2, text3) \ 144 | do \ 145 | { \ 146 | ptr += "\n"); \ 164 | ptr += buf; \ 165 | } while (0) 166 | #define ADD_CONFIG_RADIO2(name, value, fmt, desc, text0, text1) \ 167 | do \ 168 | { \ 169 | ptr += "\n"); \ 179 | ptr += buf; \ 180 | } while (0) 181 | 182 | ADD_CONFIG("hostname", current_config.hostname, "%s", "Hostname"); 183 | ADD_CONFIG("verbose", current_config.verbose, "%d", "Verbosity (1=log)"); 184 | ADD_CONFIG_RADIO4("rf1_cfg", current_config.rf1_cfg, "%d", "RF1 path", "Open", "RFO1", "50 Ω", "RFO2"); 185 | ADD_CONFIG_RADIO4("rf2_cfg", current_config.rf2_cfg, "%d", "RF2 path", "Open", "RFO1", "50 Ω", "RFO2"); 186 | ADD_CONFIG_RADIO4("rf3_cfg", current_config.rf3_cfg, "%d", "RF3 path", "Open", "RFO1", "50 Ω", "RFO2"); 187 | ADD_CONFIG_RADIO4("rf4_cfg", current_config.rf4_cfg, "%d", "RF4 path", "Open", "RFO1", "50 Ω", "RFO2"); 188 | ADD_CONFIG_RADIO2("rfo1_amp", current_config.rfo1_amp, "%d", "RFO1 Amplifier", "Off", "On"); 189 | ADD_CONFIG_RADIO2("rfo2_amp", current_config.rfo2_amp, "%d", "RFO2 Amplifier", "Off", "On"); 190 | 191 | ptr += "
" desc ":
"; \ 147 | sprintf(buf, "\n", (value==0)?"checked":""); \ 148 | ptr += buf; \ 149 | sprintf(buf, "\n"); \ 150 | ptr += buf; \ 151 | sprintf(buf, "\n", (value==1)?"checked":""); \ 152 | ptr += buf; \ 153 | sprintf(buf, "\n"); \ 154 | ptr += buf; \ 155 | sprintf(buf, "\n", (value==2)?"checked":""); \ 156 | ptr += buf; \ 157 | sprintf(buf, "\n"); \ 158 | ptr += buf; \ 159 | sprintf(buf, "\n", (value==3)?"checked":""); \ 160 | ptr += buf; \ 161 | sprintf(buf, "\n"); \ 162 | ptr += buf; \ 163 | sprintf(buf, "
" desc ":
"; \ 170 | sprintf(buf, "\n", (value==0)?"checked":""); \ 171 | ptr += buf; \ 172 | sprintf(buf, "\n"); \ 173 | ptr += buf; \ 174 | sprintf(buf, "\n", (value==1)?"checked":""); \ 175 | ptr += buf; \ 176 | sprintf(buf, "\n"); \ 177 | ptr += buf; \ 178 | sprintf(buf, "
\n"; 192 | 193 | ptr += "\n"; 194 | ptr += "\n"; 195 | return ptr; 196 | } 197 | --------------------------------------------------------------------------------