Browse Source

Added milligram css; repo cleanup

master
chodak166 4 years ago
parent
commit
36bfb084e6
  1. 1
      components/firmware/CMakeLists.txt
  2. 17
      components/firmware/include/nx/firmware/Buttons.h
  3. 10
      components/firmware/include/nx/firmware/GpioTrigger.h
  4. 2
      components/firmware/include/nx/firmware/Wifi.h
  5. 33
      components/firmware/src/Buttons.c
  6. 66
      components/firmware/src/GpioTrigger.c
  7. 8
      components/firmware/src/StatusIndicator.c
  8. 9
      components/firmware/src/Wifi.c
  9. 1
      components/software/CMakeLists.txt
  10. 1
      components/software/include/nx/software/AppSettings.h
  11. 10
      components/software/include/nx/software/CommandParser.h
  12. 7
      components/software/src/AppSettings.c
  13. 40
      components/software/src/CommandParser.c
  14. 38
      components/software/src/SystemSettings.c
  15. 151
      main/Main.c
  16. 6
      main/static/app.html
  17. 2576
      main/static/index.css
  18. 38
      main/static/index.html
  19. 31
      main/static/index.js
  20. 3
      main/static/min/app.html
  21. 2
      main/static/min/index.css
  22. 3
      main/static/min/index.html
  23. 15
      main/static/min/index.js
  24. 0
      pcb/case/CaseBody.scad
  25. 0
      pcb/case/CaseBodyExtended.scad
  26. 0
      pcb/case/CaseRoof.scad
  27. 0
      pcb/case/Cover.scad
  28. 0
      pcb/case/LedHook.scad
  29. 0
      pcb/case/RfBoard.scad
  30. 0
      pcb/case/SnapFit.scad
  31. 0
      pcb/case/TargetPcb.scad
  32. 0
      pcb/case/config.scad
  33. 0
      pcb/case/mqtrigger.stl
  34. 0
      pcb/kicad/Regulator_Switching.dcm
  35. 0
      pcb/kicad/ap1509.dcm
  36. 0
      pcb/kicad/ap1509.lib
  37. 0
      pcb/kicad/mqtrigger.kicad_pcb
  38. 0
      pcb/kicad/mqtrigger.pro
  39. 0
      pcb/kicad/mqtrigger.sch
  40. 21
      third-party/milligram/license
  41. 42
      third-party/milligram/src/_Base.sass
  42. 12
      third-party/milligram/src/_Blockquote.sass
  43. 76
      third-party/milligram/src/_Button.sass
  44. 22
      third-party/milligram/src/_Code.sass
  45. 12
      third-party/milligram/src/_Color.sass
  46. 8
      third-party/milligram/src/_Divider.sass
  47. 67
      third-party/milligram/src/_Form.sass
  48. 164
      third-party/milligram/src/_Grid.sass
  49. 6
      third-party/milligram/src/_Image.sass
  50. 11
      third-party/milligram/src/_Link.sass
  51. 22
      third-party/milligram/src/_List.sass
  52. 27
      third-party/milligram/src/_Spacing.sass
  53. 27
      third-party/milligram/src/_Table.sass
  54. 48
      third-party/milligram/src/_Typography.sass
  55. 18
      third-party/milligram/src/_Utility.sass
  56. 19
      third-party/milligram/src/milligram.sass

1
components/firmware/CMakeLists.txt

@ -9,6 +9,7 @@ idf_component_register(
src/SntpClient.c src/SntpClient.c
src/StatusIndicator.c src/StatusIndicator.c
src/Restarter.c src/Restarter.c
src/GpioTrigger.c
INCLUDE_DIRS ./include INCLUDE_DIRS ./include
PRIV_INCLUDE_DIRS ./src PRIV_INCLUDE_DIRS ./src

17
components/firmware/include/nx/firmware/Buttons.h

@ -3,13 +3,22 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h>
#define BUTTON_SERVICE_ID 1
#define BUTTON_SERVICE_GPIO_NUM 23
typedef void (*ButtonEventHandler)(uint8_t btn); typedef void (*ButtonEventHandler)(uint8_t btn);
void nxInitButtons(void); typedef struct Button
{
uint8_t id;
uint8_t btnGpio;
uint8_t pullGpio; // disabled when 0
bool inverted; // triggered by gnd by default
bool isPressed;
bool isBouncing;
uint32_t debounceMs;
} Button;
void nxInitButtons(Button buttons[], size_t buttonCount);
void nxSetOnPressHandler(ButtonEventHandler handler); void nxSetOnPressHandler(ButtonEventHandler handler);

10
components/firmware/include/nx/firmware/GpioTrigger.h

@ -0,0 +1,10 @@
#ifndef COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_GPIOTRIGGER_H_
#define COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_GPIOTRIGGER_H_
#include <stdint.h>
void nxInitGpioTrigger(const uint8_t* pins, uint8_t count);
void nxTriggerGpio(uint8_t outputIndex, uint32_t durationMs);
void nxUpdateTriggerGpio(uint8_t outputIndex, uint8_t level);
#endif /* COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_GPIOTRIGGER_H_ */

2
components/firmware/include/nx/firmware/Wifi.h

@ -32,6 +32,6 @@ bool nxInitWifi(WifiSettings* wifiSettings);
void nxSetWifiConnectedCallback(WifiCallback callback); void nxSetWifiConnectedCallback(WifiCallback callback);
void nxSetWifiErrorCallback(WifiCallback callback); void nxSetWifiErrorCallback(WifiCallback callback);
const char* nxGetWifiDeviceName(void); const char* nxGetWifiDeviceName(void);
bool nxIsWifiStaConnected(void);
#endif // COMPONENTS_FIRMWARE_WIFI_H #endif // COMPONENTS_FIRMWARE_WIFI_H

33
components/firmware/src/Buttons.c

@ -18,30 +18,8 @@
#define RTOS_TASK_STACK_SIZE 4096 // stack overflow when less #define RTOS_TASK_STACK_SIZE 4096 // stack overflow when less
#define RTOS_TASK_PRIORITY 10 #define RTOS_TASK_PRIORITY 10
typedef struct Button Button* buttons = NULL;
{ static size_t buttonCount = 0;
uint8_t id;
uint8_t btnGpio;
uint8_t pullGpio; // disabled when 0
bool inverted; // triggered by gnd by default
bool isPressed;
bool isBouncing;
uint32_t debounceMs;
} Button;
static Button buttons[] = {
{
.id = BUTTON_SERVICE_ID,
.btnGpio = BUTTON_SERVICE_GPIO_NUM,
.pullGpio = 0,
.inverted = false,
.isPressed = false,
.isBouncing = false,
.debounceMs = 50
}
};
const size_t buttonCount = sizeof(buttons)/sizeof(buttons[0]);
static ButtonEventHandler pressHandler = NULL; static ButtonEventHandler pressHandler = NULL;
static ButtonEventHandler releaseHandler = NULL; static ButtonEventHandler releaseHandler = NULL;
@ -85,14 +63,18 @@ static void buttonQueueTask(void* arg)
// --------- Public API --------- // // --------- Public API --------- //
void nxInitButtons(void) void nxInitButtons(Button* buttonArray, size_t count)
{ {
buttons = buttonArray;
buttonCount = count;
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT); gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
for (size_t i = 0; i < buttonCount; ++i) { for (size_t i = 0; i < buttonCount; ++i) {
Button* button = &buttons[i]; Button* button = &buttons[i];
gpio_reset_pin(button->btnGpio); gpio_reset_pin(button->btnGpio);
gpio_set_direction(button->btnGpio, GPIO_MODE_INPUT); gpio_set_direction(button->btnGpio, GPIO_MODE_INPUT);
if (button->pullGpio) { if (button->pullGpio) {
gpio_reset_pin(button->pullGpio); gpio_reset_pin(button->pullGpio);
gpio_set_direction(button->pullGpio, GPIO_MODE_OUTPUT); gpio_set_direction(button->pullGpio, GPIO_MODE_OUTPUT);
@ -139,4 +121,3 @@ bool nxIsButtonPressed(uint8_t btn)
} }

66
components/firmware/src/GpioTrigger.c

@ -0,0 +1,66 @@
#include "nx/firmware/GpioTrigger.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_log.h>
#include <esp_system.h>
#include <driver/gpio.h>
static int MAX_TRIGGER_DURATION_MS = 20000;
static const char* TAG = "NX_TRIGGER";
static const uint8_t* outputs = NULL;
static size_t outputCount = 0;
void nxInitGpioTrigger(const uint8_t* pins, uint8_t count)
{
outputs = pins;
outputCount = count;
for (uint8_t i = 0; i < count; ++i) {
ESP_LOGI(TAG, "Initializing GPIO %i (index %i)", outputs[i], i);
gpio_reset_pin(outputs[i]);
gpio_set_direction(outputs[i], GPIO_MODE_OUTPUT);
gpio_set_level(outputs[i], 0);
}
}
void nxTriggerGpio(uint8_t outputIndex, uint32_t durationMs)
{
if (outputs == NULL) {
ESP_LOGE(TAG, "Trigger outputs not set, aborting");
return;
}
if (outputIndex >= outputCount) {
ESP_LOGE(TAG, "Trigger output index out of range, aborting");
return;
}
if (durationMs > MAX_TRIGGER_DURATION_MS) {
durationMs = MAX_TRIGGER_DURATION_MS;
}
ESP_LOGI(TAG, "Triggering output %i for %ims", outputs[outputIndex], durationMs);
gpio_set_level(outputs[outputIndex], 1);
vTaskDelay(durationMs / portTICK_PERIOD_MS);
gpio_set_level(outputs[outputIndex], 0);
}
void nxUpdateTriggerGpio(uint8_t outputIndex, uint8_t level)
{
if (outputs == NULL) {
ESP_LOGE(TAG, "Trigger outputs not set, aborting");
return;
}
if (outputIndex >= outputCount) {
ESP_LOGE(TAG, "Trigger output index out of range, aborting");
return;
}
ESP_LOGI(TAG, "Updating output %i to level %i", outputs[outputIndex], level);
gpio_set_level(outputs[outputIndex], level);
}

8
components/firmware/src/StatusIndicator.c

@ -203,8 +203,12 @@ void nxUpdateStatus(HwStatus newStatus)
{ {
// priority filters: // priority filters:
if (currentStatus == STATUS_SYSTEM_ERROR if (currentStatus == STATUS_SYSTEM_ERROR
&& newStatus == STATUS_APP_ERROR) { && newStatus == STATUS_APP_ERROR) {
return; return;
}
if (currentStatus == STATUS_OK_WORKING
&& newStatus == STATUS_OK_WORKING) {
return;
} }
prevStatus = currentStatus; prevStatus = currentStatus;
currentStatus = newStatus; currentStatus = newStatus;

9
components/firmware/src/Wifi.c

@ -42,6 +42,7 @@ static WifiSettings* settings = NULL;
static WifiCallback errorCallback = NULL; static WifiCallback errorCallback = NULL;
static WifiCallback connectedCallback = NULL; static WifiCallback connectedCallback = NULL;
static bool staConnected = false;
static char deviceName[DEVICE_NAME_MAX_LEN] = {0}; static char deviceName[DEVICE_NAME_MAX_LEN] = {0};
@ -94,6 +95,8 @@ static void handleStaEvents(esp_event_base_t event_base, int32_t event_id, void*
} }
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGE(TAG, "Cannot connect to the AP"); ESP_LOGE(TAG, "Cannot connect to the AP");
staConnected = false;
if (errorCallback) { if (errorCallback) {
errorCallback(); errorCallback();
} }
@ -113,6 +116,7 @@ static void handleStaEvents(esp_event_base_t event_base, int32_t event_id, void*
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "IP address acquired: " IPSTR, IP2STR(&event->ip_info.ip)); ESP_LOGI(TAG, "IP address acquired: " IPSTR, IP2STR(&event->ip_info.ip));
staConnected = true;
retryCount = 0; retryCount = 0;
if (connectedCallback) { if (connectedCallback) {
connectedCallback(); connectedCallback();
@ -354,3 +358,8 @@ const char* nxGetWifiDeviceName(void)
return "unknown"; return "unknown";
} }
bool nxIsWifiStaConnected(void)
{
return staConnected;
}

1
components/software/CMakeLists.txt

@ -5,6 +5,7 @@ idf_component_register(
src/KeyValueParser.c src/KeyValueParser.c
src/AppSettings.c src/AppSettings.c
src/AppSettingsApi.c src/AppSettingsApi.c
src/CommandParser.c
INCLUDE_DIRS ./include INCLUDE_DIRS ./include
PRIV_INCLUDE_DIRS ./src PRIV_INCLUDE_DIRS ./src

1
components/software/include/nx/software/AppSettings.h

@ -28,6 +28,7 @@ typedef struct AppSettings {
bool overrideDevName; bool overrideDevName;
char customDevName[DEVICE_NAME_MAX_LEN]; char customDevName[DEVICE_NAME_MAX_LEN];
char caCert[CA_CERT_MAX_SIZE]; char caCert[CA_CERT_MAX_SIZE];
uint16_t wdogMaxSec;
} AppSettings; } AppSettings;

10
components/software/include/nx/software/CommandParser.h

@ -0,0 +1,10 @@
#ifndef COMPONENTS_SOFTWARE_INCLUDE_NX_SOFTWARE_COMMANDPARSER_H_
#define COMPONENTS_SOFTWARE_INCLUDE_NX_SOFTWARE_COMMANDPARSER_H_
#include <stdint.h>
typedef void (*CommandHandler)(const char* cmd, const char** args, uint8_t argc);
void nxParseCommandString(const char* commandString, CommandHandler handler);
#endif /* COMPONENTS_SOFTWARE_INCLUDE_NX_SOFTWARE_COMMANDPARSER_H_ */

7
components/software/src/AppSettings.c

@ -13,6 +13,7 @@
#define KEY_OVR_DEVNAME "ovdn" #define KEY_OVR_DEVNAME "ovdn"
#define KEY_CUSTOM_DEVNAME "cdn" #define KEY_CUSTOM_DEVNAME "cdn"
#define KEY_CA_CERT "cacert" #define KEY_CA_CERT "cacert"
#define KEY_WDOG_MAX_SEC "wdogs"
#define STORAGE_READ(_KEY, _NAME)\ #define STORAGE_READ(_KEY, _NAME)\
storageRead(_KEY, &settings._NAME, sizeof(settings._NAME)); storageRead(_KEY, &settings._NAME, sizeof(settings._NAME));
@ -32,6 +33,7 @@ static const char* DEFAULT_MQTT_PASS = "";
static const bool DEFAULT_OV_DEVNAME = false; static const bool DEFAULT_OV_DEVNAME = false;
static const char* DEFAULT_CUSTOM_DEVNAME = "esp-dev"; static const char* DEFAULT_CUSTOM_DEVNAME = "esp-dev";
static const char* DEFAULT_CA_CERT = "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n"; static const char* DEFAULT_CA_CERT = "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n";
static const uint16_t DEFAULT_WDOG_MAX_SEC = 60 * 15;
static StorageWriteFn storageWrite = NULL; static StorageWriteFn storageWrite = NULL;
@ -62,6 +64,7 @@ static void loadSettings(void)
STORAGE_READ(KEY_OVR_DEVNAME , overrideDevName ); STORAGE_READ(KEY_OVR_DEVNAME , overrideDevName );
STORAGE_READ(KEY_CUSTOM_DEVNAME , customDevName ); STORAGE_READ(KEY_CUSTOM_DEVNAME , customDevName );
STORAGE_READ(KEY_CA_CERT , caCert ); STORAGE_READ(KEY_CA_CERT , caCert );
STORAGE_READ(KEY_WDOG_MAX_SEC , wdogMaxSec );
} }
@ -99,6 +102,7 @@ void nxRestoreAppDefaultSettings(void)
settings.mqttHbIntervalSec = DEFAULT_MQTT_HB_SEC; settings.mqttHbIntervalSec = DEFAULT_MQTT_HB_SEC;
settings.mqttUseTls = DEFAULT_MQTT_TLS; settings.mqttUseTls = DEFAULT_MQTT_TLS;
settings.overrideDevName = DEFAULT_OV_DEVNAME; settings.overrideDevName = DEFAULT_OV_DEVNAME;
settings.wdogMaxSec = DEFAULT_WDOG_MAX_SEC;
strcpy(settings.mqttHbUri , DEFAULT_MQTT_HB_URI ); strcpy(settings.mqttHbUri , DEFAULT_MQTT_HB_URI );
strcpy(settings.mqttApiUri , DEFAULT_MQTT_API_URI ); strcpy(settings.mqttApiUri , DEFAULT_MQTT_API_URI );
@ -108,7 +112,6 @@ void nxRestoreAppDefaultSettings(void)
strcpy(settings.customDevName , DEFAULT_CUSTOM_DEVNAME); strcpy(settings.customDevName , DEFAULT_CUSTOM_DEVNAME);
strcpy(settings.caCert , DEFAULT_CA_CERT ); strcpy(settings.caCert , DEFAULT_CA_CERT );
nxWriteAppSettings(); nxWriteAppSettings();
} }
@ -128,6 +131,8 @@ void nxWriteAppSettings(void)
STORAGE_WRITE(KEY_CA_CERT , caCert ); STORAGE_WRITE(KEY_CA_CERT , caCert );
STORAGE_WRITE(KEY_CUSTOM_DEVNAME , customDevName ); STORAGE_WRITE(KEY_CUSTOM_DEVNAME , customDevName );
STORAGE_WRITE(KEY_OVR_DEVNAME , overrideDevName ); STORAGE_WRITE(KEY_OVR_DEVNAME , overrideDevName );
STORAGE_WRITE(KEY_WDOG_MAX_SEC , wdogMaxSec );
settingsUpdatedCb(); settingsUpdatedCb();
} }

40
components/software/src/CommandParser.c

@ -0,0 +1,40 @@
#include "nx/software/CommandParser.h"
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
static const char CMD_SEPARATOR = ' ';
void nxParseCommandString(const char* commandString, CommandHandler handler)
{
char* cmd = malloc(strlen(commandString)+1);
if (!cmd) {
return;
}
strcpy(cmd, commandString);
uint8_t argc = 0;
const char** args = NULL;
char* arg = cmd;
while ( (arg = strchr(arg, CMD_SEPARATOR)) ) {
arg[0] = '\0';
++arg;
argc += 1;
const char** nArgs = calloc(argc, sizeof(char*));
for (int i = 0; i < argc-1; ++i) {
nArgs[i] = args[i];
}
nArgs[argc-1] = arg;
if (args) {
free(args);
}
args = nArgs;
}
handler(cmd, args, argc);
free(args);
free(cmd);
}

38
components/software/src/SystemSettings.c

@ -54,18 +54,17 @@ static bool firstRun(void)
static void loadSystemSettings(void) static void loadSystemSettings(void)
{ {
printf("Loading system settings\n"); printf("Loading system settings\n");
storageRead(KEY_WIFI_SSID , settings.wifiSsid , WIFI_STRINGS_MAX_LEN); STORAGE_READ(KEY_WIFI_SSID , wifiSsid );
storageRead(KEY_WIFI_PASS , settings.wifiPassword , WIFI_STRINGS_MAX_LEN); STORAGE_READ(KEY_WIFI_PASS , wifiPassword );
storageRead(KEY_WIFI_ADDR , settings.ip4addr , IPV4_ADDR_SIZE);
storageRead(KEY_WIFI_GW , settings.ip4gw , IPV4_ADDR_SIZE);
storageRead(KEY_WIFI_MASK , settings.ip4mask , IPV4_ADDR_SIZE);
storageRead(KEY_DNS_ADDR , settings.dnsAddr , IPV4_ADDR_SIZE);
storageRead(KEY_SNTP_ADDR , settings.sntpAddr , URI_MAX_LEN);
storageRead(KEY_TZ_ENV , settings.tzEnv , TZ_ENV_LEN);
storageRead(KEY_RS_SCHEDULE , settings.rsSchedule , RESTART_SCHEDULE_SIZE);
STORAGE_READ(KEY_WIFI_POWER_SAVE, wifiPowerSave); STORAGE_READ(KEY_WIFI_POWER_SAVE, wifiPowerSave);
STORAGE_READ(KEY_WIFI_USE_STATIC, useStaticAddr); STORAGE_READ(KEY_WIFI_USE_STATIC, useStaticAddr);
STORAGE_READ(KEY_WIFI_ADDR , ip4addr );
STORAGE_READ(KEY_WIFI_GW , ip4gw );
STORAGE_READ(KEY_WIFI_MASK , ip4mask );
STORAGE_READ(KEY_DNS_ADDR , dnsAddr );
STORAGE_READ(KEY_SNTP_ADDR , sntpAddr );
STORAGE_READ(KEY_TZ_ENV , tzEnv );
STORAGE_READ(KEY_RS_SCHEDULE , rsSchedule );
} }
@ -115,18 +114,17 @@ void nxWriteSystemSettings(void)
printf("WRITING SYSTEM SETTINGS\n"); printf("WRITING SYSTEM SETTINGS\n");
storageWrite(KEY_INIT_FLAG, &INIT_FLAG_VALUE, 1); storageWrite(KEY_INIT_FLAG, &INIT_FLAG_VALUE, 1);
storageWrite(KEY_WIFI_SSID , settings.wifiSsid , WIFI_STRINGS_MAX_LEN ); STORAGE_WRITE(KEY_WIFI_SSID , wifiSsid );
storageWrite(KEY_WIFI_PASS , settings.wifiPassword , WIFI_STRINGS_MAX_LEN ); STORAGE_WRITE(KEY_WIFI_PASS , wifiPassword );
storageWrite(KEY_WIFI_ADDR , settings.ip4addr , IPV4_ADDR_SIZE );
storageWrite(KEY_WIFI_GW , settings.ip4gw , IPV4_ADDR_SIZE );
storageWrite(KEY_WIFI_MASK , settings.ip4mask , IPV4_ADDR_SIZE );
storageWrite(KEY_DNS_ADDR , settings.dnsAddr , IPV4_ADDR_SIZE );
storageWrite(KEY_SNTP_ADDR , settings.sntpAddr , URI_MAX_LEN );
storageWrite(KEY_TZ_ENV , settings.tzEnv , TZ_ENV_LEN );
storageWrite(KEY_RS_SCHEDULE , settings.rsSchedule , RESTART_SCHEDULE_SIZE );
STORAGE_WRITE(KEY_WIFI_POWER_SAVE, wifiPowerSave ); STORAGE_WRITE(KEY_WIFI_POWER_SAVE, wifiPowerSave );
STORAGE_WRITE(KEY_WIFI_USE_STATIC, useStaticAddr ); STORAGE_WRITE(KEY_WIFI_USE_STATIC, useStaticAddr );
STORAGE_WRITE(KEY_WIFI_ADDR , ip4addr );
STORAGE_WRITE(KEY_WIFI_GW , ip4gw );
STORAGE_WRITE(KEY_WIFI_MASK , ip4mask );
STORAGE_WRITE(KEY_DNS_ADDR , dnsAddr );
STORAGE_WRITE(KEY_SNTP_ADDR , sntpAddr );
STORAGE_WRITE(KEY_TZ_ENV , tzEnv );
STORAGE_WRITE(KEY_RS_SCHEDULE , rsSchedule );
} }
SystemSettings* nxGetSystemSettings(void) SystemSettings* nxGetSystemSettings(void)

151
main/Main.c

@ -2,6 +2,7 @@
#include "nx/software/SystemSettingsApi.h" #include "nx/software/SystemSettingsApi.h"
#include "nx/software/AppSettings.h" #include "nx/software/AppSettings.h"
#include "nx/software/AppSettingsApi.h" #include "nx/software/AppSettingsApi.h"
#include "nx/software/CommandParser.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
@ -14,6 +15,8 @@
#include "nx/firmware/SntpClient.h" #include "nx/firmware/SntpClient.h"
#include "nx/firmware/StatusIndicator.h" #include "nx/firmware/StatusIndicator.h"
#include "nx/firmware/Restarter.h" #include "nx/firmware/Restarter.h"
#include "nx/firmware/GpioTrigger.h"
#include "nx/firmware/Buttons.h"
#include "HttpHandlers.h" #include "HttpHandlers.h"
@ -27,9 +30,16 @@
#define API_QOS 2 #define API_QOS 2
#define HB_QOS 1 #define HB_QOS 1
#define BUTTON_SERVICE_ID 1
#define BUTTON_SERVICE_GPIO_NUM 15
#define SERVICE_BUTTON_DEFAULT_RESET_HOLD_SEC 1500
static const char* DEVICE_NAME_PREFIX = "mqt-"; static const char* DEVICE_NAME_PREFIX = "mqt-";
static void restoreDefaultsOnStartupButtonHold(void);
static void startWifi(void); static void startWifi(void);
static void onCommandRequest(const uint8_t* content, size_t ctLen,
const char** response, size_t* respLen);
static void onWifiConnected(void); static void onWifiConnected(void);
static void onWifiError(void); static void onWifiError(void);
static void onAppSettingsUpdate(void); static void onAppSettingsUpdate(void);
@ -39,6 +49,7 @@ static void onMqttError();
static void onMqttMessage(const char* msg); static void onMqttMessage(const char* msg);
static void hbTask(void*); static void hbTask(void*);
static void connWatchdogTask(void*);
static SystemSettings* systemSettings = NULL; static SystemSettings* systemSettings = NULL;
static AppSettings* appSettings = NULL; static AppSettings* appSettings = NULL;
@ -47,6 +58,7 @@ static MqttSettings mqttSettings;
static bool wifiStarted = false; static bool wifiStarted = false;
static bool mqttStarted = false; static bool mqttStarted = false;
static bool serviceMode = false;
static const MqTriggerHttpCallbacks httpCallbacks = { static const MqTriggerHttpCallbacks httpCallbacks = {
.getRoot = rootHandler, .getRoot = rootHandler,
@ -57,22 +69,37 @@ static const MqTriggerHttpCallbacks httpCallbacks = {
.getSysSet = nxApiGetSystemSettings, .getSysSet = nxApiGetSystemSettings,
.postSysSet = nxApiUpdateSystemSettings, .postSysSet = nxApiUpdateSystemSettings,
.getAppSet = nxApiGetAppSettings, .getAppSet = nxApiGetAppSettings,
.postAppSet = nxApiUpdateAppSettings .postAppSet = nxApiUpdateAppSettings,
// .postCmd = nxApiHandleCmd .postCmd = onCommandRequest
};
static Button buttons[] = {
{
.id = BUTTON_SERVICE_ID,
.btnGpio = BUTTON_SERVICE_GPIO_NUM,
.pullGpio = 0,
.inverted = false,
.isPressed = false,
.isBouncing = false,
.debounceMs = 50
}
}; };
static const uint8_t TRIGGER_GPIO[] = {27, 26, 25, 33};
void app_main(void) void app_main(void)
{ {
nxInitStatusIndicator(); nxInitStatusIndicator();
nxUpdateStatus(STATUS_BOOT); nxUpdateStatus(STATUS_BOOT);
xTaskCreate(&nxStatusIndicatorTask, "status_task", 2048, NULL, 1, NULL); xTaskCreate(&nxStatusIndicatorTask, "status_task", 2048, NULL, 1, NULL);
nxInitButtons(buttons, sizeof(buttons)/sizeof(buttons[0]));
nxInitStorage(); nxInitStorage();
// uncomment to force default NVS initialization for development // uncomment to force default NVS initialization for development
// uint8_t zero = 0; // uint8_t zero = 0;
// nxStorageWrite("ledinit", &zero, 1); // nxStorageWrite("ledinit", &zero, 1);
// nxStorageWrite("sysinit", &zero, 1); // nxStorageWrite("sysinit", &zero, 1);
nxInitSystemSettings(nxStorageWrite, nxStorageRead); nxInitSystemSettings(nxStorageWrite, nxStorageRead);
nxInitAppSettings(nxStorageWrite, nxStorageRead, onAppSettingsUpdate); nxInitAppSettings(nxStorageWrite, nxStorageRead, onAppSettingsUpdate);
@ -80,20 +107,55 @@ void app_main(void)
systemSettings = nxGetSystemSettings(); systemSettings = nxGetSystemSettings();
appSettings = nxGetAppSettings(); appSettings = nxGetAppSettings();
restoreDefaultsOnStartupButtonHold();
nxInitGpioTrigger(TRIGGER_GPIO, sizeof(TRIGGER_GPIO));
// uncomment to force WiFi settings for development // uncomment to force WiFi settings for development
// strcpy(systemSettings->wifiSsid, "androidAp"); strcpy(systemSettings->wifiSsid, "cintra");
// strcpy(systemSettings->wifiPassword, "password"); strcpy(systemSettings->wifiPassword, "fffefdfcfb");
// systemSettings->useStaticAddr = false; systemSettings->useStaticAddr = false;
nxStartRestarter(systemSettings->rsSchedule, systemSettings->tzEnv); nxStartRestarter(systemSettings->rsSchedule, systemSettings->tzEnv);
xTaskCreate(&connWatchdogTask, "conn_watchdog_task", 2048, NULL, 1, NULL);
nxSetWifiConnectedCallback(onWifiConnected); nxSetWifiConnectedCallback(onWifiConnected);
nxSetWifiErrorCallback(onWifiError); nxSetWifiErrorCallback(onWifiError);
startWifi(); startWifi();
} }
// ----- // -----
static void restoreDefaultsOnStartupButtonHold(void)
{
vTaskDelay(1000 / portTICK_PERIOD_MS); // possible debouncing capacitor wait
if (nxIsButtonPressed(BUTTON_SERVICE_ID)) {
ESP_LOGI(TAG, "SERVICE BUTTON IS PRESSED");
}
else {
ESP_LOGI(TAG, "SERVICE BUTTON IS NOT PRESSED");
}
const uint16_t stepMs = 500;
uint16_t steps = (SERVICE_BUTTON_DEFAULT_RESET_HOLD_SEC * 1000) / stepMs;
serviceMode = nxIsButtonPressed(BUTTON_SERVICE_ID);
while (nxIsButtonPressed(BUTTON_SERVICE_ID)) {
if (steps == 0) {
ESP_LOGW(TAG, "RESTORING DEFAULT SYSTEM AND DISPLAY SETTINGS");
nxRestoreAppDefaultSettings();
nxRestoreSystemDefaultSettings();
vTaskDelay(1000 / portTICK_PERIOD_MS);
esp_restart();
}
ESP_LOGW(TAG, "HOLDING SERVICE BUTTON [COUNTDOWN: %i]", steps);
vTaskDelay(stepMs / portTICK_PERIOD_MS);
steps -= 1;
}
}
static void startWifi(void) static void startWifi(void)
{ {
wifiSettings = (struct WifiSettings){ wifiSettings = (struct WifiSettings){
@ -108,10 +170,48 @@ static void startWifi(void)
.dns = systemSettings->dnsAddr .dns = systemSettings->dnsAddr
}; };
wifiSettings.mode = serviceMode ? NX_WIFI_MODE_AP : NX_WIFI_MODE_STA;
ESP_LOGI(TAG, "Initializing WiFi"); ESP_LOGI(TAG, "Initializing WiFi");
if (!nxInitWifi(&wifiSettings)) { if (!nxInitWifi(&wifiSettings)) {
nxUpdateStatus(STATUS_SYSTEM_ERROR); nxUpdateStatus(STATUS_SYSTEM_ERROR);
} }
nxEnablePowerSavingStatus(systemSettings->wifiPowerSave);
}
static void handleCommand(const char* cmd, const char* args[], uint8_t argc)
{
ESP_LOGI(TAG, "Handling command %s with %i args", cmd, argc);
if (strcmp(cmd, "trigger") == 0 && argc >= 2) {
uint32_t durationMs = atoi(args[1]);
nxTriggerGpio(atoi(args[0]), durationMs);
}
else if (strcmp(cmd, "on") == 0 && argc >= 1) {
nxUpdateTriggerGpio(atoi(args[0]), 1);
}
else if (strcmp(cmd, "off") == 0 && argc >= 1) {
nxUpdateTriggerGpio(atoi(args[0]), 0);
}
else if (strcmp(cmd, "setsys") == 0 && argc >= 1) {
nxApiUpdateSystemSettings((uint8_t*)args[0], strlen(args[0]), NULL, NULL);
}
else if (strcmp(cmd, "setapp") == 0 && argc >= 1) {
nxApiUpdateAppSettings((uint8_t*)args[0], strlen(args[0]), NULL, NULL);
}
else if (strcmp(cmd, "reboot") == 0) {
esp_restart();
}
else {
ESP_LOGW(TAG, "Unknown command or missing args: %s", cmd);
}
}
static void onCommandRequest(const uint8_t* content, size_t ctLen,
const char** response, size_t* respLen)
{
nxUpdateStatus(STATUS_OK_WORKING);
nxParseCommandString((const char*)content, handleCommand);
} }
static void onWifiConnected(void) static void onWifiConnected(void)
@ -135,7 +235,7 @@ static void onWifiConnected(void)
strcpy(mqttSettings.password, appSettings->mqttPassword); strcpy(mqttSettings.password, appSettings->mqttPassword);
strcpy(mqttSettings.clientId, appSettings->overrideDevName strcpy(mqttSettings.clientId, appSettings->overrideDevName
? appSettings->customDevName ? appSettings->customDevName
: systemSettings->deviceName); : systemSettings->deviceName);
mqttSettings.hbIntervalSec = appSettings->mqttHbIntervalSec; mqttSettings.hbIntervalSec = appSettings->mqttHbIntervalSec;
mqttSettings.caCrt = appSettings->mqttUseTls ? appSettings->caCert : NULL; mqttSettings.caCrt = appSettings->mqttUseTls ? appSettings->caCert : NULL;
@ -162,6 +262,7 @@ static void onMqttMessage(const char* msg)
{ {
ESP_LOGI(TAG, "MQTT MESSAGE RECEIVED: %s", msg); ESP_LOGI(TAG, "MQTT MESSAGE RECEIVED: %s", msg);
nxUpdateStatus(STATUS_OK_WORKING); nxUpdateStatus(STATUS_OK_WORKING);
nxParseCommandString(msg, handleCommand);
} }
static void onWifiError() static void onWifiError()
@ -232,3 +333,35 @@ static void hbTask(void* param)
vTaskDelay(appSettings->mqttHbIntervalSec*1000 / portTICK_PERIOD_MS); vTaskDelay(appSettings->mqttHbIntervalSec*1000 / portTICK_PERIOD_MS);
} }
} }
static void connWatchdogTask(void* param)
{
if (appSettings->wdogMaxSec == 0) {
ESP_LOGI(TAG, "Connection watchdog disabled");
vTaskDelete(NULL);
return;
}
ESP_LOGI(TAG, "Starting connection watchdog task");
vTaskDelay(1000 * 20 / portTICK_PERIOD_MS); // wait for initialization
uint32_t fails = 0;
const uint32_t WD_DELAY_SEC = 10;
const uint32_t MAX_FAILS = appSettings->wdogMaxSec/WD_DELAY_SEC;
while (1) {
if (!nxMqttIsConnected() || !nxIsWifiStaConnected()) {
ESP_LOGI(TAG, "Connection watchdog: DEVICE NOT CONNECTED TO WIFI/MQTT BROKER");
fails += 1;
if (fails >= MAX_FAILS) {
ESP_LOGW(TAG, "MAX CONNECTION FAILED CHECKS REACHED, REBOOTING");
esp_restart();
}
}
else if (fails > 0){
fails = 0;
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}

6
main/static/app.html

@ -69,6 +69,12 @@
</label> </label>
</p> </p>
<p>
<label>Max offline time before auto reboot (sec, 0 to disable)
<input type="number" name="wdogs">
</label>
</p>
<p> <p>
<button type="button" class="send">SEND</button> <button type="button" class="send">SEND</button>
</p> </p>

2576
main/static/index.css

File diff suppressed because it is too large Load Diff

38
main/static/index.html

@ -6,34 +6,42 @@
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo="> <link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=">
<link rel="stylesheet" href="index.css" /> <link rel="stylesheet" href="index.css" />
<script type="text/javascript" src="index.js"></script> <script type="text/javascript" src="index.js"></script>
</head> </head>
<body> <body>
<div class=".float-left">
MqTrigger v1.1
</div>
<header class="sticky row"> <header class="sticky row">
<div class="col-sm col-md-10 col-md-offset-1">
<a id="app-btn" href="#" role="button" class="nav-btn" data-dst="/app/form">App</a> <div class="container nav-bar">
<a href="#" role="button" class="nav-btn" data-dst="/sys/form">System</a> <ul>
<!-- <a href="#" role="button" class="nav-btn" data-dst="/test-form.html">TEST FORM</a> --> <li>
<a id="app-btn" href="#" role="button" class="nav-btn" data-dst="/app/form">Application</a>
</li>
<li>
<a href="#" role="button" class="nav-btn" data-dst="/sys/form">System</a>
</li>
<!-- <a href="#" role="button" class="nav-btn" data-dst="/test-form.html">TEST FORM</a> -->
</ul>
</div> </div>
</header> </header>
<div class="container"> <div class="container">
<div class="row cols-sm-12 cols-md-10"> <div class="col-md-offset-1" id="content">
<h1>Loading</h1>
<div class="col-md-offset-1" id="content"> <p>Loading content... Please make sure that JavaScript is enabled.</p>
<h1>Loading</h1>
<p>Loading content... Please make sure that JavaScript is enabled.</p>
</div>
<div class="col-md-offset-1"> <hr/> </div>
</div> </div>
</div> </div>
<hr />
<footer> <footer>
<div class="col-sm col-md-10 col-md-offset-1"> <div class="container">
<p> <p>
MqTrigger v1.1 2022 Copyright&copy; nixlab.in 2022
</p> </p>
</div> </div>
</footer> </footer>

31
main/static/index.js

@ -5,23 +5,16 @@ function ip2int(ip) {
} }
function sendClickHandler(event) { function sendClickHandler(event) {
console.log("Sending form...");
//console.debug(event);
let form = event.srcElement.form; let form = event.srcElement.form;
console.debug(form); console.debug(form);
let params = ''; let params = '';
Array.from(form.elements).forEach(element => { Array.from(form.elements).forEach(element => {
console.log(element);
console.log(element.name);
console.log(element.value);
if (element.value) { //TODO: checkbox/radio if (element.value) { //TODO: checkbox/radio
if (!element.hasAttribute('data-ignore')) { if (!element.hasAttribute('data-ignore')) {
params += params ? '&' : ''; params += params ? '&' : '';
if (element.hasAttribute('data-ip32')) { if (element.hasAttribute('data-ip32')) {
console.log("IPv4 32");
params += element.name + '=' + ip2int(element.value); params += element.name + '=' + ip2int(element.value);
} }
else { else {
@ -31,16 +24,9 @@ function sendClickHandler(event) {
else { else {
console.log("Ignoring " + element.name); console.log("Ignoring " + element.name);
} }
} }
}); });
console.log("params: " + params);
postParams(form.getAttribute('action'), params); postParams(form.getAttribute('action'), params);
console.log("Form sent");
} }
function initFormSendButtons(form) { function initFormSendButtons(form) {
@ -62,7 +48,6 @@ function loadContent(url) {
http.onreadystatechange = function () {//Call a function when the state changes. http.onreadystatechange = function () {//Call a function when the state changes.
if (http.readyState == 4 && http.status == 200) { if (http.readyState == 4 && http.status == 200) {
console.log("Content received");
element.innerHTML = http.responseText; element.innerHTML = http.responseText;
loadValues(element); loadValues(element);
initFormSendButtons(element); initFormSendButtons(element);
@ -71,11 +56,10 @@ function loadContent(url) {
http.send(); http.send();
} }
function fillInputs(element, jsonString) { function fillInputs(jsonString) {
let obj = JSON.parse(jsonString); let obj = JSON.parse(jsonString);
if (obj) { if (obj) {
for (var key of Object.keys(obj)) { for (var key of Object.keys(obj)) {
console.log(key + " -> " + obj[key])
let input = element.querySelector("[name='" + key + "'"); let input = element.querySelector("[name='" + key + "'");
if (input) { //TODO: checkbox/radio if (input) { //TODO: checkbox/radio
input.value = obj[key]; input.value = obj[key];
@ -98,13 +82,10 @@ function loadValues(element) {
} }
if (valuesCache[srcUrl]) { if (valuesCache[srcUrl]) {
console.log("Source values already cached: " + valuesCache[srcUrl]); fillInputs(valuesCache[srcUrl]);
fillInputs(element, valuesCache[srcUrl]);
continue; continue;
} }
console.log("Getting values from " + srcUrl);
let http = new XMLHttpRequest(); let http = new XMLHttpRequest();
http.open('GET', srcUrl, false); http.open('GET', srcUrl, false);
@ -113,9 +94,8 @@ function loadValues(element) {
http.onreadystatechange = function () {//Call a function when the state changes. http.onreadystatechange = function () {//Call a function when the state changes.
if (http.readyState == 4 && http.status == 200) { if (http.readyState == 4 && http.status == 200) {
console.log("Values received: " + http.responseText);
valuesCache[srcUrl] = http.responseText; valuesCache[srcUrl] = http.responseText;
fillInputs(element, http.responseText); fillInputs(http.responseText);
} }
} }
http.send(); http.send();
@ -138,8 +118,6 @@ function postParams(url, params) {
} }
function navClickHandler(event) { function navClickHandler(event) {
console.log('Button Clicked');
console.log('Destination: ' + this.getAttribute('data-dst'));
loadContent(this.getAttribute('data-dst')); loadContent(this.getAttribute('data-dst'));
} }
@ -179,5 +157,4 @@ function rschSet() {
document.getElementById("rsd_" + i.toString()).checked = true; document.getElementById("rsd_" + i.toString()).checked = true;
} }
} }
}
}

3
main/static/min/app.html

@ -11,4 +11,5 @@
<select class=select name=ovdn> <select class=select name=ovdn>
<option value=0>No <option value=0>No
<option value=1>Yes</select></label><p><label>Custom device name: <option value=1>Yes</select></label><p><label>Custom device name:
<input name=cdn></label><p><label>Certificate data (begin to end, tags included):<p><textarea name=cacert></textarea></label><p><button type=button class=send>SEND</button></p>NOTE: device reboot is required</form><div class=col-md-offset-1><hr></div><h3>Restore defaults</h3><form id=restore-form action=/app><input type=hidden name=restore_default value=1><p><button type=button class=send>RESTORE DEFAULT SETTINGS</button></form><div class=col-md-offset-1><hr></div><h3>Reboot</h3><form id=reboot-form action=/sys/reboot><input type=hidden name=reboot value=1><p><button type=button class=send>REBOOT</button></form> <input name=cdn></label><p><label>Certificate data (begin to end, tags included):<p><textarea name=cacert></textarea></label><p><label>Max offline time before auto reboot (sec, 0 to disable)
<input type=number name=wdogs></label><p><button type=button class=send>SEND</button></p>NOTE: device reboot is required</form><div class=col-md-offset-1><hr></div><h3>Restore defaults</h3><form id=restore-form action=/app><input type=hidden name=restore_default value=1><p><button type=button class=send>RESTORE DEFAULT SETTINGS</button></form><div class=col-md-offset-1><hr></div><h3>Reboot</h3><form id=reboot-form action=/sys/reboot><input type=hidden name=reboot value=1><p><button type=button class=send>REBOOT</button></form>

2
main/static/min/index.css

File diff suppressed because one or more lines are too long

3
main/static/min/index.html

@ -1,2 +1 @@
<meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon type=image/png href=data:image/png,%89PNG%0D%0A%1A%0A><link rel=stylesheet href=index.css><script src=index.js></script><header class="sticky row"><div class="col-sm col-md-10 col-md-offset-1"><a id=app-btn href=# role=button class=nav-btn data-dst=/app/form>App</a> <meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon type=image/png href=data:image/png,%89PNG%0D%0A%1A%0A><link rel=stylesheet href=index.css><script src=index.js></script><div class=.float-left>MqTrigger v1.1</div><header class="sticky row"><div class="container nav-bar"><ul><li><a id=app-btn href=# role=button class=nav-btn data-dst=/app/form>Application</a><li><a href=# role=button class=nav-btn data-dst=/sys/form>System</a></ul></div></header><div class=container><div class=col-md-offset-1 id=content><h1>Loading</h1><p>Loading content... Please make sure that JavaScript is enabled.</div></div><hr><footer><div class=container><p>Copyright&copy; nixlab.in 2022</div></footer><script>initNavButtons();document.getElementById('app-btn').click();</script>
<a href=# role=button class=nav-btn data-dst=/sys/form>System</a></div></header><div class=container><div class="row cols-sm-12 cols-md-10"><div class=col-md-offset-1 id=content><h1>Loading</h1><p>Loading content... Please make sure that JavaScript is enabled.</div><div class=col-md-offset-1><hr></div></div></div><footer><div class="col-sm col-md-10 col-md-offset-1"><p>MqTrigger v1.1 2022</div></footer><script>initNavButtons();document.getElementById('app-btn').click();</script>

15
main/static/min/index.js

@ -1,19 +1,18 @@
var valuesCache={};function ip2int(ip){return ip.split('.').reduce(function(ipInt,octet){return(ipInt<<8)+parseInt(octet,10)},0)>>>0;} var valuesCache={};function ip2int(ip){return ip.split('.').reduce(function(ipInt,octet){return(ipInt<<8)+parseInt(octet,10)},0)>>>0;}
function sendClickHandler(event){console.log("Sending form...");let form=event.srcElement.form;console.debug(form);let params='';Array.from(form.elements).forEach(element=>{console.log(element);console.log(element.name);console.log(element.value);if(element.value){if(!element.hasAttribute('data-ignore')){params+=params?'&':'';if(element.hasAttribute('data-ip32')){console.log("IPv4 32");params+=element.name+'='+ip2int(element.value);} function sendClickHandler(event){let form=event.srcElement.form;console.debug(form);let params='';Array.from(form.elements).forEach(element=>{if(element.value){if(!element.hasAttribute('data-ignore')){params+=params?'&':'';if(element.hasAttribute('data-ip32')){params+=element.name+'='+ip2int(element.value);}
else{params+=element.name+'='+element.value;}} else{params+=element.name+'='+element.value;}}
else{console.log("Ignoring "+element.name);}}});console.log("params: "+params);postParams(form.getAttribute('action'),params);console.log("Form sent");} else{console.log("Ignoring "+element.name);}}});postParams(form.getAttribute('action'),params);}
function initFormSendButtons(form){const btns=form.querySelectorAll('.send');for(i=0;i<btns.length;++i){btns[i].addEventListener('click',sendClickHandler);}} function initFormSendButtons(form){const btns=form.querySelectorAll('.send');for(i=0;i<btns.length;++i){btns[i].addEventListener('click',sendClickHandler);}}
function loadContent(url){let http=new XMLHttpRequest();http.open('GET',url,true);let element=document.getElementById('content');element.innerHTML="LOADING...";http.onreadystatechange=function(){if(http.readyState==4&&http.status==200){console.log("Content received");element.innerHTML=http.responseText;loadValues(element);initFormSendButtons(element);}} function loadContent(url){let http=new XMLHttpRequest();http.open('GET',url,true);let element=document.getElementById('content');element.innerHTML="LOADING...";http.onreadystatechange=function(){if(http.readyState==4&&http.status==200){element.innerHTML=http.responseText;loadValues(element);initFormSendButtons(element);}}
http.send();} http.send();}
function fillInputs(element,jsonString){let obj=JSON.parse(jsonString);if(obj){for(var key of Object.keys(obj)){console.log(key+" -> "+obj[key]) function fillInputs(jsonString){let obj=JSON.parse(jsonString);if(obj){for(var key of Object.keys(obj)){let input=element.querySelector("[name='"+key+"'");if(input){input.value=obj[key];if(input.hasAttribute('data-onset')){eval(input.getAttribute("data-onset"));}}}}}
let input=element.querySelector("[name='"+key+"'");if(input){input.value=obj[key];if(input.hasAttribute('data-onset')){eval(input.getAttribute("data-onset"));}}}}}
function loadValues(element){valuesCache={};const forms=element.querySelectorAll('form');for(i=0;i<forms.length;++i){const srcUrl=forms[i].getAttribute('data-values-src');if(!srcUrl){continue;} function loadValues(element){valuesCache={};const forms=element.querySelectorAll('form');for(i=0;i<forms.length;++i){const srcUrl=forms[i].getAttribute('data-values-src');if(!srcUrl){continue;}
if(valuesCache[srcUrl]){console.log("Source values already cached: "+valuesCache[srcUrl]);fillInputs(element,valuesCache[srcUrl]);continue;} if(valuesCache[srcUrl]){fillInputs(valuesCache[srcUrl]);continue;}
console.log("Getting values from "+srcUrl);let http=new XMLHttpRequest();http.open('GET',srcUrl,false);http.onreadystatechange=function(){if(http.readyState==4&&http.status==200){console.log("Values received: "+http.responseText);valuesCache[srcUrl]=http.responseText;fillInputs(element,http.responseText);}} let http=new XMLHttpRequest();http.open('GET',srcUrl,false);http.onreadystatechange=function(){if(http.readyState==4&&http.status==200){valuesCache[srcUrl]=http.responseText;fillInputs(http.responseText);}}
http.send();}} http.send();}}
function postParams(url,params){var http=new XMLHttpRequest();http.open('POST',url,true);http.setRequestHeader('Content-type','application/x-www-form-urlencoded');http.onreadystatechange=function(){if(http.readyState==4&&http.status==200){alert(http.responseText);}} function postParams(url,params){var http=new XMLHttpRequest();http.open('POST',url,true);http.setRequestHeader('Content-type','application/x-www-form-urlencoded');http.onreadystatechange=function(){if(http.readyState==4&&http.status==200){alert(http.responseText);}}
http.send(params);} http.send(params);}
function navClickHandler(event){console.log('Button Clicked');console.log('Destination: '+this.getAttribute('data-dst'));loadContent(this.getAttribute('data-dst'));} function navClickHandler(event){loadContent(this.getAttribute('data-dst'));}
function initNavButtons(){const btns=document.querySelectorAll('.nav-btn');for(i=0;i<btns.length;++i){btns[i].addEventListener('click',navClickHandler);}} function initNavButtons(){const btns=document.querySelectorAll('.nav-btn');for(i=0;i<btns.length;++i){btns[i].addEventListener('click',navClickHandler);}}
function getRschDays(){let daysBin=0;for(let i=0;i<7;++i){let day=document.getElementById("rsd_"+i.toString());if(day&&day.checked===true){daysBin|=(1<<i);}} function getRschDays(){let daysBin=0;for(let i=0;i<7;++i){let day=document.getElementById("rsd_"+i.toString());if(day&&day.checked===true){daysBin|=(1<<i);}}
return daysBin;} return daysBin;}

0
case/CaseBody.scad → pcb/case/CaseBody.scad

0
case/CaseBodyExtended.scad → pcb/case/CaseBodyExtended.scad

0
case/CaseRoof.scad → pcb/case/CaseRoof.scad

0
case/Cover.scad → pcb/case/Cover.scad

0
case/LedHook.scad → pcb/case/LedHook.scad

0
case/RfBoard.scad → pcb/case/RfBoard.scad

0
case/SnapFit.scad → pcb/case/SnapFit.scad

0
case/TargetPcb.scad → pcb/case/TargetPcb.scad

0
case/config.scad → pcb/case/config.scad

0
case/mqtrigger.stl → pcb/case/mqtrigger.stl

0
kicad/Regulator_Switching.dcm → pcb/kicad/Regulator_Switching.dcm

0
kicad/ap1509.dcm → pcb/kicad/ap1509.dcm

0
kicad/ap1509.lib → pcb/kicad/ap1509.lib

0
kicad/mqtrigger.kicad_pcb → pcb/kicad/mqtrigger.kicad_pcb

0
kicad/mqtrigger.pro → pcb/kicad/mqtrigger.pro

0
kicad/mqtrigger.sch → pcb/kicad/mqtrigger.sch

21
third-party/milligram/license vendored

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) CJ Patoilo <cjpatoilo@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

42
third-party/milligram/src/_Base.sass vendored

@ -0,0 +1,42 @@
// Base
//
// Set box-sizing globally to handle padding and border widths
*,
*:after,
*:before
box-sizing: inherit
// The base font-size is set at 62.5% for having the convenience
// of sizing rems in a way that is similar to using px: 1.6rem = 16px
html
box-sizing: border-box
font-size: 62.5%
// Default body styles
body
color: $color-secondary
background-color: $color-background
font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif
font-size: 1.6em // Currently ems cause chrome bug misinterpreting rems on body element
font-weight: 300
letter-spacing: .01em
line-height: 1.6
.nav-bar
background-color: $color-initial
top: 0
left: 0
right: 0
border-bottom: 1px solid $color-tertiary;
height: 3rem
display: flex
text-transform: uppercase
margin-bottom: 2rem !important
*
display: inline
li
margin: 1rem
a
font-weight: bold

12
third-party/milligram/src/_Blockquote.sass vendored

@ -0,0 +1,12 @@
// Blockquote
//
blockquote
border-left: .3rem solid $color-quaternary
margin-left: 0
margin-right: 0
padding: 1rem 1.5rem
*:last-child
margin-bottom: 0

76
third-party/milligram/src/_Button.sass vendored

@ -0,0 +1,76 @@
// Button
//
.button,
button,
input[type='button'],
input[type='reset'],
input[type='submit']
background-color: $color-primary
border: .1rem solid $color-primary
border-radius: .4rem
color: $color-initial
cursor: pointer
display: inline-block
font-size: 1.1rem
font-weight: 700
height: 3.8rem
letter-spacing: .1rem
line-height: 3.8rem
padding: 0 3.0rem
text-align: center
text-decoration: none
text-transform: uppercase
white-space: nowrap
&:focus,
&:hover
background-color: $color-secondary
border-color: $color-secondary
color: $color-initial
outline: 0
&[disabled]
cursor: default
opacity: .5
&:focus,
&:hover
background-color: $color-primary
border-color: $color-primary
&.button-outline
background-color: transparent
color: $color-primary
&:focus,
&:hover
background-color: transparent
border-color: $color-secondary
color: $color-secondary
&[disabled]
&:focus,
&:hover
border-color: inherit
color: $color-primary
&.button-clear
background-color: transparent
border-color: transparent
color: $color-primary
&:focus,
&:hover
background-color: transparent
border-color: transparent
color: $color-secondary
&[disabled]
&:focus,
&:hover
color: $color-primary

22
third-party/milligram/src/_Code.sass vendored

@ -0,0 +1,22 @@
// Code
//
code
background: $color-tertiary
border-radius: .4rem
font-size: 86%
margin: 0 .2rem
padding: .2rem .5rem
white-space: nowrap
pre
background: $color-tertiary
border-left: .3rem solid $color-primary
overflow-y: hidden
& > code
border-radius: 0
display: block
padding: 1rem 1.5rem
white-space: pre

12
third-party/milligram/src/_Color.sass vendored

@ -0,0 +1,12 @@
// Color
//
$color-initial: #fafafa !default
$color-primary: #FF7E5F!default
$color-secondary: #351C4D !default
$color-tertiary: #b0b0b0 !default
$color-quaternary: #d1d1d1 !default
$color-quinary: #e1e1e1 !default
$color-background: #fafafa !default

8
third-party/milligram/src/_Divider.sass vendored

@ -0,0 +1,8 @@
// Divider
//
hr
border: 0
border-top: .1rem solid $color-tertiary
margin: 3.0rem 0

67
third-party/milligram/src/_Form.sass vendored

@ -0,0 +1,67 @@
// Form
//
input[type='color'],
input[type='date'],
input[type='datetime'],
input[type='datetime-local'],
input[type='email'],
input[type='month'],
input[type='number'],
input[type='password'],
input[type='search'],
input[type='tel'],
input[type='text'],
input[type='url'],
input[type='week'],
input:not([type]),
textarea,
select
-webkit-appearance: none // sass-lint:disable-line no-vendor-prefixes
background-color: transparent
border: .1rem solid $color-quaternary
border-radius: .4rem
box-shadow: none
box-sizing: inherit // Forced to replace inherit values of the normalize.css
height: 3.8rem
padding: .6rem 1.0rem .7rem // This vertically centers text on FF, ignored by Webkit
width: 100%
&:focus
border-color: $color-primary
outline: 0
select
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 8" width="30"><path fill="%23' + str-slice(inspect($color-quaternary), 2) + '" d="M0,0l6,8l6-8"/></svg>') center right no-repeat
padding-right: 3.0rem
&:focus
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 8" width="30"><path fill="%23' + str-slice(inspect($color-primary), 2) + '" d="M0,0l6,8l6-8"/></svg>')
&[multiple]
background: none
height: auto
textarea
min-height: 6.5rem
label,
legend
display: block
font-size: 1.6rem
font-weight: 700
margin-bottom: .5rem
fieldset
border-width: 0
padding: 0
input[type='checkbox'],
input[type='radio']
display: inline
.label-inline
display: inline-block
font-weight: normal
margin-left: .5rem

164
third-party/milligram/src/_Grid.sass vendored

@ -0,0 +1,164 @@
// Grid
//
// .container is main centered wrapper with a max width of 112.0rem (1120px)
.container
margin: 0 auto
max-width: 112.0rem
padding: 0 2.0rem
position: relative
width: 100%
// Using flexbox for the grid, inspired by Philip Walton:
// http://philipwalton.github.io/solved-by-flexbox/demos/grids/
// By default each .column within a .row will evenly take up
// available width, and the height of each .column with take
// up the height of the tallest .column in the same .row
.row
display: flex
flex-direction: column
padding: 0
width: 100%
&.row-no-padding
padding: 0
&> .column
padding: 0
&.row-wrap
flex-wrap: wrap
// Vertically Align Columns
// .row-* vertically aligns every .col in the .row
&.row-top
align-items: flex-start
&.row-bottom
align-items: flex-end
&.row-center
align-items: center
&.row-stretch
align-items: stretch
&.row-baseline
align-items: baseline
.column
display: block
// IE 11 required specifying the flex-basis otherwise it breaks mobile
flex: 1 1 auto
margin-left: 0
max-width: 100%
width: 100%
// Column Offsets
&.column-offset-10
margin-left: 10%
&.column-offset-20
margin-left: 20%
&.column-offset-25
margin-left: 25%
&.column-offset-33,
&.column-offset-34
margin-left: 33.3333%
&.column-offset-40
margin-left: 40%
&.column-offset-50
margin-left: 50%
&.column-offset-60
margin-left: 60%
&.column-offset-66,
&.column-offset-67
margin-left: 66.6666%
&.column-offset-75
margin-left: 75%
&.column-offset-80
margin-left: 80%
&.column-offset-90
margin-left: 90%
// Explicit Column Percent Sizes
// By default each grid column will evenly distribute
// across the grid. However, you can specify individual
// columns to take up a certain size of the available area
&.column-10
flex: 0 0 10%
max-width: 10%
&.column-20
flex: 0 0 20%
max-width: 20%
&.column-25
flex: 0 0 25%
max-width: 25%
&.column-33,
&.column-34
flex: 0 0 33.3333%
max-width: 33.3333%
&.column-40
flex: 0 0 40%
max-width: 40%
&.column-50
flex: 0 0 50%
max-width: 50%
&.column-60
flex: 0 0 60%
max-width: 60%
&.column-66,
&.column-67
flex: 0 0 66.6666%
max-width: 66.6666%
&.column-75
flex: 0 0 75%
max-width: 75%
&.column-80
flex: 0 0 80%
max-width: 80%
&.column-90
flex: 0 0 90%
max-width: 90%
// .column-* vertically aligns an individual .column
.column-top
align-self: flex-start
.column-bottom
align-self: flex-end
.column-center
align-self: center
// Larger than mobile screen
@media (min-width: 40.0rem) // Safari desktop has a bug using `rem`, but Safari mobile works
.row
flex-direction: row
margin-left: -1.0rem
width: calc(100% + 2.0rem)
.column
margin-bottom: inherit
padding: 0 1.0rem

6
third-party/milligram/src/_Image.sass vendored

@ -0,0 +1,6 @@
// Image
//
img
max-width: 100%

11
third-party/milligram/src/_Link.sass vendored

@ -0,0 +1,11 @@
// Link
//
a
color: $color-primary
text-decoration: none
&:focus,
&:hover
color: $color-secondary

22
third-party/milligram/src/_List.sass vendored

@ -0,0 +1,22 @@
// List
//
dl,
ol,
ul
list-style: none
margin-top: 0
padding-left: 0
dl,
ol,
ul
font-size: 90%
margin: 1.5rem 0 1.5rem 3.0rem
ol
list-style: decimal inside
ul
list-style: circle inside

27
third-party/milligram/src/_Spacing.sass vendored

@ -0,0 +1,27 @@
// Spacing
//
.button,
button,
dd,
dt,
li
margin-bottom: 1.0rem
fieldset,
input,
select,
textarea
margin-bottom: 1.5rem
blockquote,
dl,
figure,
form,
ol,
p,
pre,
table,
ul
margin-bottom: 2.5rem

27
third-party/milligram/src/_Table.sass vendored

@ -0,0 +1,27 @@
// Table
//
table
border-spacing: 0
display: block
overflow-x: auto
text-align: left
width: 100%
td,
th
border-bottom: .1rem solid $color-quinary
padding: 1.2rem 1.5rem
&:first-child
padding-left: 0
&:last-child
padding-right: 0
@media (min-width: 40.0rem)
table
display: table
overflow-x: initial

48
third-party/milligram/src/_Typography.sass vendored

@ -0,0 +1,48 @@
// Typography
//
b,
strong
font-weight: bold
p
margin-top: 0
h1,
h2,
h3,
h4,
h5,
h6
font-weight: 300
letter-spacing: -.1rem
margin-bottom: 2.0rem
margin-top: 0
h1
font-size: 4.6rem
line-height: 1.2
h2
font-size: 3.6rem
line-height: 1.25
h3
font-size: 2.8rem
line-height: 1.3
h4
font-size: 2.2rem
letter-spacing: -.08rem
line-height: 1.35
h5
font-size: 1.8rem
letter-spacing: -.05rem
line-height: 1.5
h6
font-size: 1.6rem
letter-spacing: 0
line-height: 1.4

18
third-party/milligram/src/_Utility.sass vendored

@ -0,0 +1,18 @@
// Utility
//
// Clear a float with .clearfix
.clearfix
&:after
clear: both
content: ' ' // The space content is one way to avoid an Opera bug.
display: table
// Float either direction
.float-left
float: left
.float-right
float: right

19
third-party/milligram/src/milligram.sass vendored

@ -0,0 +1,19 @@
// Modules
//
@import _Color
@import _Base
@import _Blockquote
@import _Button
@import _Code
@import _Divider
@import _Form
@import _Grid
@import _Link
@import _List
@import _Spacing
@import _Table
@import _Typography
@import _Image
@import _Utility
Loading…
Cancel
Save