16 changed files with 578 additions and 36 deletions
@ -0,0 +1,22 @@
|
||||
#ifndef COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_HWCONFIG_H_ |
||||
#define COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_HWCONFIG_H_ |
||||
|
||||
#define DEVICE_ID "dev01" |
||||
|
||||
#define GPIO_SERVICE_BTN 15 |
||||
|
||||
#define GPIO_TRIGGER_OUT_1 27 |
||||
#define GPIO_TRIGGER_OUT_2 26 |
||||
#define GPIO_TRIGGER_OUT_3 25 |
||||
#define GPIO_TRIGGER_OUT_4 33 |
||||
|
||||
#define USED_OUTPUTS_COUNT 4 |
||||
|
||||
#define GPIO_LED_RED 14 |
||||
#define GPIO_LED_GREEN 12 |
||||
#define GPIO_LED_BLUE 13 |
||||
|
||||
#define LED_LEVEL_ON 1 |
||||
#define LED_LEVEL_OFF 0 |
||||
|
||||
#endif /* COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_HWCONFIG_H_ */ |
||||
@ -0,0 +1,6 @@
|
||||
#ifndef COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_RESTARTER_H_ |
||||
#define COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_RESTARTER_H_ |
||||
|
||||
void nxStartRestarter(const char* schedule, const char* timezone); |
||||
|
||||
#endif /* COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_RESTARTER_H_ */ |
||||
@ -0,0 +1,20 @@
|
||||
#ifndef COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_STATUSINDICATOR_H_ |
||||
#define COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_STATUSINDICATOR_H_ |
||||
|
||||
#include <stdbool.h> |
||||
|
||||
typedef enum { |
||||
STATUS_BOOT = 0, // cont. red
|
||||
STATUS_OK, // cont. green
|
||||
STATUS_OK_WORKING, // 10Hz green
|
||||
STATUS_OK_ALT, // 1Hz blue (e.g. service mode)
|
||||
STATUS_SYSTEM_ERROR, // 1Hz red (e.g. wifi not connected)
|
||||
STATUS_APP_ERROR // 2 x 2Hz + 1s off (e.g. mqtt not connected)
|
||||
} HwStatus; |
||||
|
||||
void nxInitStatusIndicator(); |
||||
void nxUpdateStatus(HwStatus status); |
||||
void nxStatusIndicatorTask(void*); |
||||
void nxEnablePowerSavingStatus(bool enable); |
||||
|
||||
#endif /* COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_STATUSINDICATOR_H_ */ |
||||
@ -0,0 +1,95 @@
|
||||
#include "nx/firmware/Restarter.h" |
||||
|
||||
#include "esp_log.h" |
||||
#include "esp_system.h" |
||||
#include "freertos/FreeRTOS.h" |
||||
#include "freertos/task.h" |
||||
#include <time.h> |
||||
#include <string.h> |
||||
|
||||
#define TAG "RESTARTER" |
||||
|
||||
#define RESTARTER_CHECK_INTERVAL_SEC 10 |
||||
#define RESTART_SCHEDULE_SIZE 12 |
||||
#define RESTART_SCHEDULE_MIN_LEN 7 // 'x x x x'
|
||||
|
||||
const char* schedule = ""; |
||||
const char* tz = ""; |
||||
|
||||
// day - days from sunday (0 is sunday), schedule - binary schedule from sunday (1 is sunday, 64 is saturday)
|
||||
int isDayInSchedule(int day, int schedule) |
||||
{ |
||||
int dayMatch = 1 << day; |
||||
int match = (dayMatch & schedule); |
||||
return match != 0; |
||||
} |
||||
|
||||
void restarterTask(void* param) |
||||
{ |
||||
//UNUSED(param);
|
||||
ESP_LOGI(TAG, "Starting restarter task"); |
||||
|
||||
if (strlen(schedule) < RESTART_SCHEDULE_MIN_LEN) { |
||||
ESP_LOGW(TAG, "Closing restarter task due to the incomplete schedule: [%s]", schedule); |
||||
vTaskDelete( NULL ); |
||||
return; |
||||
} |
||||
|
||||
long int intervalMs = 1000 * RESTARTER_CHECK_INTERVAL_SEC; |
||||
|
||||
char scheduleLine[RESTART_SCHEDULE_SIZE]; |
||||
strcpy(scheduleLine, schedule); |
||||
|
||||
int daySchedule = atoi( strtok(scheduleLine, " ") ); |
||||
int hour = atoi( strtok(NULL, " ") ); |
||||
int minute = atoi( strtok(NULL, " ") ); |
||||
int maxUptime = atoi( strtok(NULL, " ") ); |
||||
|
||||
ESP_LOGI(TAG, "Restart schedule: %i %i %i %i", daySchedule, hour, minute, maxUptime); |
||||
|
||||
if (daySchedule == 0) { |
||||
ESP_LOGI(TAG, "Closing restarter task due to zero days in schedule"); |
||||
vTaskDelete( NULL ); |
||||
return; |
||||
} |
||||
|
||||
while (1) { |
||||
vTaskDelay(intervalMs / portTICK_PERIOD_MS); |
||||
|
||||
int uptimeMin = portTICK_PERIOD_MS * xTaskGetTickCount() / 1000 / 60; |
||||
|
||||
time_t now; |
||||
struct tm timeinfo; |
||||
time(&now); |
||||
|
||||
// Set timezone to Eastern Standard Time and print local time
|
||||
setenv("TZ", tz, 1); |
||||
tzset(); |
||||
localtime_r(&now, &timeinfo); |
||||
|
||||
if (uptimeMin < maxUptime) { |
||||
continue; |
||||
} |
||||
if (!isDayInSchedule(timeinfo.tm_wday, daySchedule)) { |
||||
continue; |
||||
} |
||||
if (hour != timeinfo.tm_hour || hour == -1) { |
||||
continue; |
||||
} |
||||
if (minute != timeinfo.tm_min) { |
||||
continue; |
||||
} |
||||
|
||||
ESP_LOGI(TAG, "all conditions met, restarting..."); |
||||
esp_restart(); |
||||
} |
||||
} |
||||
|
||||
void nxStartRestarter(const char* scheduleString, const char* timezone) |
||||
{ |
||||
schedule = scheduleString; |
||||
tz = timezone; |
||||
|
||||
xTaskCreate(restarterTask, "restarter_task", 2048, |
||||
(void*)1, tskIDLE_PRIORITY, NULL); |
||||
} |
||||
@ -0,0 +1,212 @@
|
||||
#include "nx/firmware/StatusIndicator.h" |
||||
#include "nx/firmware/HwConfig.h" |
||||
|
||||
#include "freertos/FreeRTOS.h" |
||||
#include "freertos/task.h" |
||||
#include "driver/gpio.h" |
||||
#include "esp_log.h" |
||||
#include <stdio.h> |
||||
|
||||
|
||||
#define UNUSED(x) (void)(x) |
||||
|
||||
#define BLINK_GPIO GPIO_LED_GREEN |
||||
|
||||
#define RED_ON gpio_set_level(GPIO_LED_RED, LED_LEVEL_ON) |
||||
#define RED_OFF gpio_set_level(GPIO_LED_RED, LED_LEVEL_OFF) |
||||
|
||||
#define GREEN_ON gpio_set_level(GPIO_LED_GREEN, LED_LEVEL_ON) |
||||
#define GREEN_OFF gpio_set_level(GPIO_LED_GREEN, LED_LEVEL_OFF) |
||||
|
||||
#define BLUE_ON gpio_set_level(GPIO_LED_BLUE, LED_LEVEL_ON) |
||||
#define BLUE_OFF gpio_set_level(GPIO_LED_BLUE, LED_LEVEL_OFF) |
||||
|
||||
#define BLINK_INTERVAL 500/portTICK_PERIOD_MS |
||||
|
||||
static const char* TAG = "STATUS"; |
||||
|
||||
static HwStatus prevStatus = STATUS_BOOT; |
||||
static HwStatus currentStatus = STATUS_BOOT; |
||||
|
||||
static bool powerSaving = false; |
||||
|
||||
static void statusCycleBoot() |
||||
{ |
||||
GREEN_OFF; |
||||
BLUE_OFF; |
||||
RED_ON; |
||||
vTaskDelay(BLINK_INTERVAL); |
||||
} |
||||
|
||||
static void statusCycleOk() |
||||
{ |
||||
GREEN_ON; |
||||
BLUE_OFF; |
||||
RED_OFF; |
||||
vTaskDelay(BLINK_INTERVAL); |
||||
} |
||||
|
||||
static void statusCycleOkPowerSave() |
||||
{ |
||||
GREEN_OFF; |
||||
BLUE_OFF; |
||||
RED_OFF; |
||||
|
||||
const int factor = 5; |
||||
int cycles = 20 * factor; |
||||
while (--cycles >= 0 && currentStatus == STATUS_OK) { |
||||
vTaskDelay(BLINK_INTERVAL / factor); |
||||
} |
||||
|
||||
if (currentStatus == STATUS_OK) { |
||||
GREEN_ON; |
||||
vTaskDelay(BLINK_INTERVAL / 5); |
||||
} |
||||
} |
||||
|
||||
static void statusCycleOkWorking() |
||||
{ |
||||
int cycles = 3; |
||||
while (--cycles >= 0) { |
||||
GREEN_ON; |
||||
BLUE_OFF; |
||||
RED_OFF; |
||||
vTaskDelay(BLINK_INTERVAL/10); |
||||
|
||||
GREEN_OFF; |
||||
vTaskDelay(BLINK_INTERVAL/10); |
||||
} |
||||
currentStatus = prevStatus; |
||||
} |
||||
|
||||
static void statusCycleBtEnabled() |
||||
{ |
||||
GREEN_OFF; |
||||
BLUE_ON; |
||||
RED_OFF; |
||||
vTaskDelay(BLINK_INTERVAL); |
||||
|
||||
BLUE_OFF; |
||||
vTaskDelay(BLINK_INTERVAL); |
||||
} |
||||
|
||||
static void statusCycleWifiError() |
||||
{ |
||||
GREEN_OFF; |
||||
BLUE_OFF; |
||||
RED_ON; |
||||
vTaskDelay(BLINK_INTERVAL); |
||||
|
||||
RED_OFF; |
||||
vTaskDelay(BLINK_INTERVAL); |
||||
} |
||||
|
||||
static void statusCycleMqttError() |
||||
{ |
||||
GREEN_OFF; |
||||
BLUE_OFF; |
||||
|
||||
RED_ON; |
||||
vTaskDelay(BLINK_INTERVAL/2); |
||||
RED_OFF; |
||||
vTaskDelay(BLINK_INTERVAL/2); |
||||
|
||||
RED_ON; |
||||
vTaskDelay(BLINK_INTERVAL/2); |
||||
RED_OFF; |
||||
vTaskDelay(BLINK_INTERVAL*2); |
||||
} |
||||
|
||||
static void printStatus() |
||||
{ |
||||
ets_printf("TRIGGER STATUS: "); |
||||
switch (currentStatus) { |
||||
case STATUS_BOOT: |
||||
ets_printf("BOOT"); |
||||
break; |
||||
case STATUS_OK: |
||||
ets_printf("OK"); |
||||
break; |
||||
case STATUS_OK_WORKING: |
||||
ets_printf("OK WORKING"); |
||||
break; |
||||
case STATUS_OK_ALT: |
||||
ets_printf("BT ENABLED"); |
||||
break; |
||||
case STATUS_SYSTEM_ERROR: |
||||
ets_printf("WIFI ERROR"); |
||||
break; |
||||
case STATUS_APP_ERROR: |
||||
ets_printf("MQTT ERROR"); |
||||
break; |
||||
} |
||||
ets_printf("\n"); |
||||
} |
||||
|
||||
void nxEnablePowerSavingStatus(bool enable) |
||||
{ |
||||
powerSaving = enable; |
||||
} |
||||
|
||||
void nxStatusIndicatorTask(void* params) |
||||
{ |
||||
UNUSED(params); |
||||
|
||||
ESP_LOGI(TAG, "STATUS INIT"); |
||||
|
||||
while(1) { |
||||
switch (currentStatus) { |
||||
case STATUS_BOOT: |
||||
statusCycleBoot(); |
||||
break; |
||||
case STATUS_OK: |
||||
if (powerSaving == true) { |
||||
statusCycleOkPowerSave(); |
||||
} |
||||
else { |
||||
statusCycleOk(); |
||||
} |
||||
break; |
||||
case STATUS_OK_WORKING: |
||||
statusCycleOkWorking(); |
||||
break; |
||||
case STATUS_OK_ALT: |
||||
statusCycleBtEnabled(); |
||||
break; |
||||
case STATUS_SYSTEM_ERROR: |
||||
statusCycleWifiError(); |
||||
break; |
||||
case STATUS_APP_ERROR: |
||||
statusCycleMqttError(); |
||||
break; |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
void nxInitStatusIndicator() |
||||
{ |
||||
gpio_reset_pin(GPIO_LED_RED); |
||||
gpio_reset_pin(GPIO_LED_GREEN); |
||||
gpio_reset_pin(GPIO_LED_BLUE); |
||||
|
||||
gpio_set_direction(GPIO_LED_RED, GPIO_MODE_OUTPUT); |
||||
gpio_set_direction(GPIO_LED_GREEN, GPIO_MODE_OUTPUT); |
||||
gpio_set_direction(GPIO_LED_BLUE, GPIO_MODE_OUTPUT); |
||||
|
||||
GREEN_OFF; |
||||
BLUE_OFF; |
||||
RED_ON; |
||||
} |
||||
|
||||
void nxUpdateStatus(HwStatus newStatus) |
||||
{ |
||||
// priority filters:
|
||||
if (currentStatus == STATUS_SYSTEM_ERROR |
||||
&& newStatus == STATUS_APP_ERROR) { |
||||
return; |
||||
} |
||||
prevStatus = currentStatus; |
||||
currentStatus = newStatus; |
||||
printStatus(); |
||||
} |
||||
@ -1,14 +1,23 @@
|
||||
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){params+=params?'&':'';if(element.hasAttribute('data-ip32')){console.log("IPv4 32");params+=element.name+'='+ip2int(element.value);} |
||||
else{params+=element.name+'='+element.value;}}});console.log("params: "+params);postParams(form.getAttribute('action'),params);console.log("Form sent");} |
||||
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);} |
||||
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");} |
||||
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);}} |
||||
http.send();} |
||||
function loadValues(element){const forms=element.querySelectorAll('form');for(i=0;i<forms.length;++i){const srcUrl=forms[i].getAttribute('data-values-src');if(!srcUrl){continue;} |
||||
console.log("Getting values from "+srcUrl);let http=new XMLHttpRequest();http.open('GET',srcUrl,true);http.onreadystatechange=function(){if(http.readyState==4&&http.status==200){console.log("Values received: "+http.responseText);let obj=JSON.parse(http.responseText);if(obj){for(var key of Object.keys(obj)){console.log(key+" -> "+obj[key]) |
||||
let input=element.querySelector("[name='"+key+"'");if(input){input.value=obj[key];}}}}} |
||||
let input=element.querySelector("[name='"+key+"'");if(input){input.value=obj[key];if(input.hasAttribute('data-onset')){eval(input.getAttribute("data-onset"));}}}}}} |
||||
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);}} |
||||
http.send(params);} |
||||
function navClickHandler(event){console.log('Button Clicked');console.log('Destination: '+this.getAttribute('data-dst'));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);}} |
||||
return daysBin;} |
||||
function rschUpdate(){let rschElem=document.getElementById("rsch");rschElem.value=getRschDays() |
||||
+" " |
||||
+document.getElementById("rsch_time").value.replace(":"," ") |
||||
+" " |
||||
+document.getElementById("rsch_rup").value;} |
||||
function rschSet(){let data=document.getElementById("rsch").value.split(" ");document.getElementById("rsch_time").value=data[1]+":"+data[2];document.getElementById("rsch_rup").value=data[3];for(let i=0;i<7;++i){if(data[0]&(1<<i)){document.getElementById("rsd_"+i.toString()).checked=true;}}} |
||||
Loading…
Reference in new issue