initial import
parent
dcbcae5c0c
commit
3af643d418
|
@ -0,0 +1,11 @@
|
||||||
|
.tscache
|
||||||
|
.project
|
||||||
|
arduino/main/main.ino.nodemcu.bin
|
||||||
|
htdocs
|
||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
tmp
|
||||||
|
electronics/electronics-cache.lib
|
||||||
|
electronics/electronics.kicad_prl
|
||||||
|
electronics/fp-info-cache
|
||||||
|
electronics/sym-lib-table
|
|
@ -0,0 +1,117 @@
|
||||||
|
module.exports = function(grunt) {
|
||||||
|
|
||||||
|
// Project configuration.
|
||||||
|
grunt.initConfig({
|
||||||
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
|
copy: {
|
||||||
|
release: {
|
||||||
|
files: [
|
||||||
|
{ src: "node_modules/jquery/dist/jquery.min.js", dest: "htdocs/jquery.min.js" },
|
||||||
|
{ src: "node_modules/jquery-toast-plugin/dist/jquery.toast.min.js", dest: "htdocs/jquery.toast.min.js" },
|
||||||
|
{ src: "node_modules/jquery-toast-plugin/dist/jquery.toast.min.css", dest: "htdocs/jquery.toast.min.css" },
|
||||||
|
{ src: "node_modules/mustache/mustache.min.js", dest: "htdocs/mustache.min.js" },
|
||||||
|
{ cwd: "js/templates/", src:"*.mst", dest: "htdocs/", expand: true},
|
||||||
|
{ src: "node_modules/mustache/mustache.min.js", dest: "htdocs/mustache.min.js" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
static: {
|
||||||
|
files: [
|
||||||
|
{ cwd: "static/", src: "**/*", dest: "htdocs/", expand:true},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ts: {
|
||||||
|
default: {
|
||||||
|
src: ['ts/**/*.ts'],
|
||||||
|
outDir: 'tmp/',
|
||||||
|
options: {
|
||||||
|
module: 'none',
|
||||||
|
moduleResolution: 'node',
|
||||||
|
sourceMap: true,
|
||||||
|
target: 'es5',
|
||||||
|
rootDir: ['ts']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
uglify: {
|
||||||
|
options: {
|
||||||
|
beautify: true,
|
||||||
|
mangle:false,
|
||||||
|
sourceMap: false
|
||||||
|
},
|
||||||
|
bootstrap: {
|
||||||
|
files: {
|
||||||
|
'htdocs/bootstrap.min.js': ['tmp/view.js','tmp/*.js'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
jsbootstrapts: {
|
||||||
|
files: ['ts/*.ts'],
|
||||||
|
tasks: ['ts', 'newer:uglify:bootstrap']
|
||||||
|
},
|
||||||
|
|
||||||
|
lessdefault: {
|
||||||
|
files: ['less/*.less'],
|
||||||
|
tasks: ['less']
|
||||||
|
},
|
||||||
|
concat: {
|
||||||
|
files: ['templates/base/*'],
|
||||||
|
tasks: ['concat']
|
||||||
|
},
|
||||||
|
jstemplate: {
|
||||||
|
files: ['templates/*.mst'],
|
||||||
|
tasks: ['copy']
|
||||||
|
},
|
||||||
|
json_merge: {
|
||||||
|
files: ["lang/*/*.json"],
|
||||||
|
tasks: ['json_merge']
|
||||||
|
},
|
||||||
|
static: {
|
||||||
|
files: ['static/**/*'],
|
||||||
|
tasks: ['copy:static']
|
||||||
|
},
|
||||||
|
},
|
||||||
|
less: {
|
||||||
|
default: {
|
||||||
|
options: {
|
||||||
|
"strictImports": true,
|
||||||
|
"compress": true
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
"htdocs/default.css": "less/default.less"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
concat: {
|
||||||
|
options: {
|
||||||
|
sourceMap: false
|
||||||
|
},
|
||||||
|
base: { src: ['templates/base/*'], dest: 'htdocs/base.mst'},
|
||||||
|
},
|
||||||
|
json_merge: {
|
||||||
|
options: {
|
||||||
|
replacer: null,
|
||||||
|
space: " "
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
files: [
|
||||||
|
{ 'htdocs/en_main.json': ['lang/en/generic.json','lang/en/menu.json', 'lang/en/wifi.json', 'lang/en/error.json', 'lang/en/mapping.json', 'lang/en/main.json', 'lang/en/update.json'] },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-less');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||||
|
grunt.loadNpmTasks('grunt-newer');
|
||||||
|
grunt.loadNpmTasks('grunt-json-merge');
|
||||||
|
grunt.loadNpmTasks("grunt-ts");
|
||||||
|
|
||||||
|
// Default task(s).
|
||||||
|
grunt.registerTask('default', ['less', 'concat', 'ts', 'newer:uglify', 'json_merge', 'copy', 'watch']);
|
||||||
|
grunt.registerTask('release', ['less', 'concat', 'ts', 'uglify', 'json_merge', 'copy']);
|
||||||
|
};
|
|
@ -0,0 +1,2 @@
|
||||||
|
filter=-build/include_subdir,-whitespace/line_length
|
||||||
|
root=./
|
|
@ -0,0 +1 @@
|
||||||
|
abc123;42758142061871992313;client
|
|
@ -0,0 +1,599 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
||||||
|
#include <ESP8266mDNS.h>
|
||||||
|
#include <ESP8266WebServer.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#include "wlanmanager.h"
|
||||||
|
|
||||||
|
#define MORSEPIN 2
|
||||||
|
#define MORSEON LOW
|
||||||
|
#define MORSEOFF HIGH
|
||||||
|
char lang[3]="en";
|
||||||
|
|
||||||
|
/// \brief handle config call
|
||||||
|
void handleConfig();
|
||||||
|
/// \brief handle SSID call
|
||||||
|
void handleSSID();
|
||||||
|
/// \brief handle wifi call and set new credentials
|
||||||
|
void handleWifi();
|
||||||
|
|
||||||
|
/// should program be run
|
||||||
|
volatile bool run;
|
||||||
|
|
||||||
|
/// translation of D0...D7 to physical pins
|
||||||
|
char pinTranslation[8] = { 16, 5, 4, 0, 2, 14, 12, 13 };
|
||||||
|
|
||||||
|
// default config expects this:
|
||||||
|
// camera @d0 + side of optocoppler
|
||||||
|
// valve1 @d1 + side of optocoppler
|
||||||
|
// valve2 @d2 + side of optocoppler
|
||||||
|
// Trigger @d3 connects to ground (uses internal pull-up)
|
||||||
|
// LED @d4 + side of LED
|
||||||
|
// Flash @d5 + side of optocoppler
|
||||||
|
// IR @d6 + side of LED
|
||||||
|
|
||||||
|
/// our local ip
|
||||||
|
char localIP[17]={0};
|
||||||
|
/// morse ip info
|
||||||
|
volatile int8_t morseIP;
|
||||||
|
/// web server
|
||||||
|
ESP8266WebServer server(80);
|
||||||
|
/// wlan manager
|
||||||
|
WLANManager wlanManager;
|
||||||
|
/// start timestamp (in local time) of the main loop
|
||||||
|
uint32_t start;
|
||||||
|
|
||||||
|
/// handle OTA update response
|
||||||
|
void handleUpdate () {
|
||||||
|
server.sendHeader("Connection", "close");
|
||||||
|
if (Update.hasError()) {
|
||||||
|
server.send(500, "application/json", "{\"error\":\"failed\"}");
|
||||||
|
} else {
|
||||||
|
server.send(200, "application/json", "{\"success\":true}");
|
||||||
|
}
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// handle OTA update
|
||||||
|
void handleUpdateProgress() {
|
||||||
|
HTTPUpload& upload = server.upload();
|
||||||
|
if (upload.status == UPLOAD_FILE_START) {
|
||||||
|
Serial.setDebugOutput(true);
|
||||||
|
WiFiUDP::stopAll();
|
||||||
|
Serial.printf("Update: %s\n", upload.filename.c_str());
|
||||||
|
uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
|
||||||
|
if (!Update.begin(maxSketchSpace)) { //start with max available size
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||||
|
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
} else if (upload.status == UPLOAD_FILE_END) {
|
||||||
|
if (Update.end(true)) { //true to set the size to the current progress
|
||||||
|
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
|
||||||
|
} else {
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
Serial.setDebugOutput(false);
|
||||||
|
}
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// setup routine
|
||||||
|
void setup() {
|
||||||
|
LittleFS.begin();
|
||||||
|
Serial.begin(115200);
|
||||||
|
wlanManager.setSSID("cameratrigger","cameratrigger"); // set default
|
||||||
|
wlanManager.loadConfigFromFile("/config"); // load from config file
|
||||||
|
wlanManager.start(); // start access point or connect to access point
|
||||||
|
uint8_t ip[4];
|
||||||
|
*((uint32_t*)&ip) = wlanManager.getIP();
|
||||||
|
sprintf(localIP,"%i.%i.%i.%i",ip[0],ip[1],ip[2],ip[3]);
|
||||||
|
Serial.println(localIP);
|
||||||
|
morseIP = 0;
|
||||||
|
server.onNotFound([]() { // If the client requests any URI
|
||||||
|
if (!handleFileRead(server.uri())) // send it if it exists
|
||||||
|
server.send(404, "text/plain", "404: Not Found"); // otherwise, respond with a 404 (Not Found) error
|
||||||
|
});
|
||||||
|
server.on("/api/config.json", HTTP_GET, handleConfig);
|
||||||
|
server.on("/api/ssid.json", HTTP_GET, handleSSID);
|
||||||
|
server.on("/api/wifi", HTTP_POST, handleWifi);
|
||||||
|
server.on("/api/pinmapping", HTTP_POST, handlePinMapping);
|
||||||
|
server.on("/api/run", HTTP_POST, handleRun);
|
||||||
|
server.on("/api/upload", HTTP_POST, handleUpdate, handleUpdateProgress);
|
||||||
|
server.on("/update", HTTP_POST, handleUpdate, handleUpdateProgress);
|
||||||
|
server.begin();
|
||||||
|
run = false;
|
||||||
|
start = millis();
|
||||||
|
pinMode(LED_BUILTIN, OUTPUT);
|
||||||
|
pinMode(D8, OUTPUT);
|
||||||
|
digitalWrite(D8, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// handle config call
|
||||||
|
void handleConfig() {
|
||||||
|
morseIP = -1;
|
||||||
|
String result = String("{\"wifimode\":\"") + (wlanManager.isAccessPoint()? "ap" : "client") + "\",\"ssid\": \"" + wlanManager.getSSID() +"\"}";
|
||||||
|
server.send(200, "application/json", result); // Send HTTP status 200 (Ok) and send some text to the browser/client
|
||||||
|
}
|
||||||
|
|
||||||
|
/// handle SSID scan call and return list of WLAN
|
||||||
|
void handleSSID() {
|
||||||
|
int n = WiFi.scanNetworks();
|
||||||
|
String out="[";
|
||||||
|
char line[1024] = {0};
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
snprintf(line,1023,"[\"%s\",%i,%i,\"%s\"]%c",WiFi.SSID(i).c_str(), WiFi.RSSI(i), WiFi.channel(i), WiFi.encryptionType(i) == ENC_TYPE_NONE ? "open": "", ((i+1<n) ? ',' : 0));
|
||||||
|
out += line;
|
||||||
|
}
|
||||||
|
out +="]";
|
||||||
|
server.send(200, "application/json", out); // Send HTTP status 200 (Ok) and send some text to the browser/client
|
||||||
|
}
|
||||||
|
|
||||||
|
/// handle wifi call and set new credentials
|
||||||
|
void handleWifi() {
|
||||||
|
char newssid[64] = {0};
|
||||||
|
char newpassword[64] = {0};
|
||||||
|
strncpy(newssid, server.arg("ssid").c_str(), 63);
|
||||||
|
strncpy(newpassword, server.arg("password").c_str(), 63);
|
||||||
|
if (server.arg("mode") == "ap") {
|
||||||
|
// set own AP credentials
|
||||||
|
wlanManager.setSSID(newssid, newpassword);
|
||||||
|
server.send(200, "application/json", "{}"); // Send HTTP status 200 (Ok) and send some text to the browser/client
|
||||||
|
wlanManager.openAP();
|
||||||
|
wlanManager.save("/config");
|
||||||
|
} else {
|
||||||
|
// try client credentials
|
||||||
|
bool success = wlanManager.connectToAp(newssid, newpassword, [](){
|
||||||
|
String out = String("{\"redirect\":\"") + toStringIp(WiFi.localIP()) + "\"}";
|
||||||
|
server.send(200, "application/json", out.c_str()); // Send HTTP status 200 (Ok) and send some text to the browser/client
|
||||||
|
wlanManager.save("/config");
|
||||||
|
delay(1000);
|
||||||
|
uint8_t ip[4];
|
||||||
|
*((uint32_t*)&ip) = wlanManager.getIP();
|
||||||
|
sprintf(localIP,"%i.%i.%i.%i",ip[0],ip[1],ip[2],ip[3]);
|
||||||
|
morseIP = 0;
|
||||||
|
});
|
||||||
|
//Serial.printf("conn to %s - %i\n",newssid, success);
|
||||||
|
if (!success) {
|
||||||
|
server.send(403, "application/json", "{\"error\":\"invalidpassword\"}"); // Send HTTP status 200 (Ok) and send some text to the browser/client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// handle of gettting pin mapping configuration
|
||||||
|
void handlePinMapping() {
|
||||||
|
morseIP = -1;
|
||||||
|
File file = LittleFS.open("/htdocs/api/pinmapping.json", "w");
|
||||||
|
const String& data = server.arg("plain");
|
||||||
|
file.write(data.c_str(), data.length());
|
||||||
|
file.close();
|
||||||
|
handleFileRead("/api/pinmapping.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Command {
|
||||||
|
/// enable: value is pin
|
||||||
|
E = 'E',
|
||||||
|
/// disable: value is pin
|
||||||
|
D = 'D',
|
||||||
|
/// trigger a Canon IR command
|
||||||
|
C = 'C',
|
||||||
|
/// trigger a Nikon IR command
|
||||||
|
N = 'N',
|
||||||
|
/// trigger a Sony IR command
|
||||||
|
S = 'S',
|
||||||
|
/// delay
|
||||||
|
d = 'd',
|
||||||
|
};
|
||||||
|
/// actual program step
|
||||||
|
struct cmd {
|
||||||
|
/// command
|
||||||
|
Command c;
|
||||||
|
/// value for command
|
||||||
|
unsigned int value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// storage for up to 256 command
|
||||||
|
cmd cmds[256];
|
||||||
|
|
||||||
|
unsigned char cmdIndex = 0;
|
||||||
|
IRAM_ATTR void triggerSequence() {
|
||||||
|
run = true;
|
||||||
|
}
|
||||||
|
void handleRun() {
|
||||||
|
morseIP = -2;
|
||||||
|
const String& data = server.arg("cmd");
|
||||||
|
cmdIndex = 0;
|
||||||
|
const char* input = data.c_str();
|
||||||
|
bool error = false;
|
||||||
|
while (*input && !error) {
|
||||||
|
bool parseValue = true;
|
||||||
|
bool advance = true;
|
||||||
|
bool enableOutput = false;
|
||||||
|
bool enableInput = false;
|
||||||
|
switch (*input) {
|
||||||
|
case 'E': // enable: value is pin
|
||||||
|
case 'D': // disable: value is pin
|
||||||
|
case 'C':
|
||||||
|
case 'N':
|
||||||
|
case 'S':
|
||||||
|
enableOutput = true;
|
||||||
|
// fall through
|
||||||
|
case 'd': // delay: value is time
|
||||||
|
cmds[cmdIndex].c = static_cast<Command>(*input);
|
||||||
|
break;
|
||||||
|
case 'T': // trigger on rising input
|
||||||
|
enableInput = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = true;
|
||||||
|
parseValue = false;
|
||||||
|
advance = false;
|
||||||
|
server.send(400, "application/json", "{\"error\":\"invalidcommand\"}");
|
||||||
|
}
|
||||||
|
if (parseValue) {
|
||||||
|
unsigned int value = 0;
|
||||||
|
++input;
|
||||||
|
while ((*input) && (*input) != ';') {
|
||||||
|
if ((*input) >= '0' && (*input) <= '9') {
|
||||||
|
value = value * 10 + ((*input)-'0');
|
||||||
|
}
|
||||||
|
if (*input)
|
||||||
|
++input;
|
||||||
|
}
|
||||||
|
cmds[cmdIndex].value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableOutput || enableInput) {
|
||||||
|
if (cmds[cmdIndex].value > 7) {
|
||||||
|
server.send(400, "application/json", "{\"error\":\"invalidpin\"}");
|
||||||
|
error = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cmds[cmdIndex].value = pinTranslation[cmds[cmdIndex].value];
|
||||||
|
if (enableOutput) {
|
||||||
|
pinMode(cmds[cmdIndex].value, OUTPUT);
|
||||||
|
} else if (enableInput) {
|
||||||
|
pinMode(cmds[cmdIndex].value, INPUT_PULLUP);
|
||||||
|
attachInterrupt(digitalPinToInterrupt(cmds[cmdIndex].value), triggerSequence, FALLING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (advance) {
|
||||||
|
if (cmdIndex < 255)
|
||||||
|
++cmdIndex;
|
||||||
|
else {
|
||||||
|
error = true;
|
||||||
|
server.send(400, "application/json", "{\"error\":\"toomanycommands\"}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (*input)
|
||||||
|
++input;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.send(200, "application/json", "{\"message\":\"ok\"}");
|
||||||
|
// wait for trigger ?
|
||||||
|
const String& trigger = server.arg("trigger");
|
||||||
|
if (trigger == "true") {
|
||||||
|
// run now
|
||||||
|
run = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// send a trigger impulse for Nikon cameras (confirmed, works)
|
||||||
|
/// \param pin which pin
|
||||||
|
/// \param reps number of repeats
|
||||||
|
void sendNikonPulse(uint8_t pin, uint8_t reps) {
|
||||||
|
// 13us on, 13us off
|
||||||
|
while (reps) {
|
||||||
|
digitalWrite(pin,HIGH);
|
||||||
|
delayMicroseconds(10);
|
||||||
|
delayTicks(40);
|
||||||
|
digitalWrite(pin,LOW);
|
||||||
|
delayMicroseconds(10);
|
||||||
|
delayTicks(39);
|
||||||
|
--reps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// send a trigger for Nikon cameras (confirmed, works)
|
||||||
|
/// \param pin which pin
|
||||||
|
void triggerNikon(uint16_t pin) { // takes 135.6ms
|
||||||
|
noInterrupts();
|
||||||
|
// SendSequence generates a full command signal by repeatedly calling SendPulse().
|
||||||
|
// The duration of each pulse and the time gaps between pulses is exactly the same as the
|
||||||
|
// values obtained from the analysis^ by oscilloscope
|
||||||
|
for(uint8_t i=0;i<2;++i) {
|
||||||
|
//pulse for 2.0 millis
|
||||||
|
sendNikonPulse(pin, 77);
|
||||||
|
//delay for 27.8 millis
|
||||||
|
//using a combination of delay() and delayMicroseconds()
|
||||||
|
delayMicroseconds(10000);
|
||||||
|
delayMicroseconds(10000);
|
||||||
|
delayMicroseconds(7786);
|
||||||
|
//on pulse for 0.4 millis
|
||||||
|
sendNikonPulse(pin, 15);
|
||||||
|
//delay for 1.5 millis
|
||||||
|
delayMicroseconds(1499);
|
||||||
|
//on pulse for 0.4 millis
|
||||||
|
sendNikonPulse(pin, 15);
|
||||||
|
//delay for 3.5 millis
|
||||||
|
delayMicroseconds(3496);
|
||||||
|
//send pulse for 0.4 millis
|
||||||
|
sendNikonPulse(pin, 15);
|
||||||
|
if(!i) {
|
||||||
|
delayMicroseconds(10000);
|
||||||
|
delayMicroseconds(10000);
|
||||||
|
delayMicroseconds(10000);
|
||||||
|
delayMicroseconds(10000);
|
||||||
|
delayMicroseconds(10000);
|
||||||
|
delayMicroseconds(10000);
|
||||||
|
delayMicroseconds(2977);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// send a 32kHz pulse
|
||||||
|
/// \param pin pin number to use
|
||||||
|
/// \param reps number of pulses
|
||||||
|
void send32kHzPulse(uint8_t pin, uint16_t reps) {
|
||||||
|
while (reps) {
|
||||||
|
digitalWrite(pin,HIGH);
|
||||||
|
delayMicroseconds(12);
|
||||||
|
delayTicks(64);
|
||||||
|
digitalWrite(pin,LOW);
|
||||||
|
delayMicroseconds(12);
|
||||||
|
delayTicks(64);
|
||||||
|
--reps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// send a trigger for Canon cameras (not confirmed)
|
||||||
|
void triggerCanon(uint16_t pin) { // takes 8.3 ms
|
||||||
|
noInterrupts();
|
||||||
|
//16 pulses for at 32kHz
|
||||||
|
send32kHzPulse(pin, 16);
|
||||||
|
delayMicroseconds(5310);
|
||||||
|
//on pulse for 0.5 millis
|
||||||
|
send32kHzPulse(pin, 16);
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get current tick count
|
||||||
|
/// \param tick count
|
||||||
|
uint32_t inline asm_ccount() {
|
||||||
|
uint32_t r;
|
||||||
|
asm volatile ("rsr %0, ccount": "=r"(r));
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
/// delay for given number of ticks
|
||||||
|
/// \param ticks number of ticks
|
||||||
|
void delayTicks(int32_t ticks) {
|
||||||
|
uint32_t expire_ticks = asm_ccount() + ticks;
|
||||||
|
do {
|
||||||
|
ticks = expire_ticks - asm_ccount();
|
||||||
|
} while(ticks>0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// send a pulse for Sony cameras
|
||||||
|
/// \param pin pin number to use
|
||||||
|
/// \param count number of pulses
|
||||||
|
void sendSonyPulse(uint16_t pin, uint16_t count) {
|
||||||
|
// warm up cache
|
||||||
|
digitalWrite(pin,LOW);
|
||||||
|
delayTicks(40);
|
||||||
|
while (count) { // 1 pulse = 7uS high, 18us Low = 25uS at 40kHz
|
||||||
|
digitalWrite(pin,HIGH);
|
||||||
|
delayMicroseconds(4);
|
||||||
|
delayTicks(39);
|
||||||
|
digitalWrite(pin,LOW);
|
||||||
|
delayMicroseconds(15);
|
||||||
|
delayTicks(50);
|
||||||
|
--count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// send a trigger for Sony cameras (not confirmed)
|
||||||
|
/// \param pin pin number to use
|
||||||
|
void triggerSony(uint16_t pin) { // takes ? 6ms
|
||||||
|
noInterrupts();
|
||||||
|
bool cmd[] = {1,0,1,1,0,1,0,0,1,0,1,1,1,0,0,0,1,1,1,1};
|
||||||
|
//2,4ms start pulse, 600uS gap, 20 bit data
|
||||||
|
sendSonyPulse(pin, 96); // 96 cycles = 2,4ms
|
||||||
|
delayMicroseconds(600);
|
||||||
|
for (uint8_t i = 0; i < 20; ++i) {
|
||||||
|
sendSonyPulse(pin, cmd[i] ? 48 : 24);
|
||||||
|
delayMicroseconds(600);
|
||||||
|
}
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// rund defined program
|
||||||
|
void runProgram() {
|
||||||
|
for (unsigned int i = 0 ; i < cmdIndex; ++i) {
|
||||||
|
switch(cmds[i].c) {
|
||||||
|
|
||||||
|
case E:
|
||||||
|
digitalWrite(cmds[i].value, true);
|
||||||
|
break;
|
||||||
|
case D:
|
||||||
|
digitalWrite(cmds[i].value, false);
|
||||||
|
break;
|
||||||
|
case C:
|
||||||
|
triggerCanon(cmds[i].value);
|
||||||
|
break;
|
||||||
|
case N:
|
||||||
|
triggerNikon(cmds[i].value);
|
||||||
|
break;
|
||||||
|
case S:
|
||||||
|
triggerSony(cmds[i].value);
|
||||||
|
break;
|
||||||
|
case d:
|
||||||
|
uint32_t d = cmds[i].value;
|
||||||
|
while (d>16000) {
|
||||||
|
d-= 16000;
|
||||||
|
delay(16000);
|
||||||
|
}
|
||||||
|
delay(d);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// guess content type based on file name
|
||||||
|
/// \param filename filename to check
|
||||||
|
/// \return content type
|
||||||
|
String getContentType(String filename) { // convert the file extension to the MIME type
|
||||||
|
if (filename.endsWith(".html")) return "text/html";
|
||||||
|
else if (filename.endsWith(".css")) return "text/css";
|
||||||
|
else if (filename.endsWith(".js")) return "application/javascript";
|
||||||
|
else if (filename.endsWith(".ico")) return "image/x-icon";
|
||||||
|
else if (filename.endsWith(".png")) return "image/png";
|
||||||
|
else if (filename.endsWith(".svg")) return "image/svg+xml";
|
||||||
|
else if (filename.endsWith(".json")) return "application/json";
|
||||||
|
return "text/plain";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// send a file to the client
|
||||||
|
/// \param path file/path to send
|
||||||
|
/// \return true on success
|
||||||
|
bool handleFileRead(String path) { // send the right file to the client (if it exists)
|
||||||
|
morseIP = -2;
|
||||||
|
if (path.endsWith("/")) path += "index.html"; // If a folder is requested, send the index file
|
||||||
|
String contentType = getContentType(path); // Get the MIME type
|
||||||
|
path = "/htdocs" + path;
|
||||||
|
if (LittleFS.exists(path)) { // If the file exists
|
||||||
|
File file = LittleFS.open(path, "r"); // Open it
|
||||||
|
server.sendHeader("X-Version", "1.2");
|
||||||
|
server.streamFile(file, contentType); // And send it to the client
|
||||||
|
file.close(); // Then close the file again
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (captivePortal()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false; // If the file doesn't exist, return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// crude check if string is an IP
|
||||||
|
/// \return true if an IP
|
||||||
|
bool isIp(String str) {
|
||||||
|
for (size_t i = 0; i < str.length(); i++) {
|
||||||
|
int c = str.charAt(i);
|
||||||
|
if (c != '.' && (c < '0' || c > '9')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// IP to String
|
||||||
|
/// \param ip IP to convert
|
||||||
|
/// return formatted string
|
||||||
|
String toStringIp(IPAddress ip) {
|
||||||
|
String res = "";
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
res += String((ip >> (8 * i)) & 0xFF) + ".";
|
||||||
|
}
|
||||||
|
res += String(((ip >> 8 * 3)) & 0xFF);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redirect to captive portal if we got a request for another domain.
|
||||||
|
/// \return true in that case so the page handler do not try to handle the request again.
|
||||||
|
bool captivePortal() {
|
||||||
|
morseIP = -2;
|
||||||
|
if (!isIp(server.hostHeader()) ) {
|
||||||
|
//Serial.println("Request redirected to captive portal");
|
||||||
|
server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()) + "/", true);
|
||||||
|
server.send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
|
||||||
|
server.client().stop(); // Stop is needed because we sent no content length
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
// char msg[4];
|
||||||
|
// int msgLen = 0;
|
||||||
|
// uint32_t nextmsg;
|
||||||
|
// bool out = false;
|
||||||
|
|
||||||
|
/// "morse" a digit (blink number of times)
|
||||||
|
/// \param count number of times to blink
|
||||||
|
void morseDigit(uint8_t count) {
|
||||||
|
while (count) {
|
||||||
|
server.handleClient();
|
||||||
|
wlanManager.loop();
|
||||||
|
digitalWrite(MORSEPIN, MORSEON);
|
||||||
|
delay(500);
|
||||||
|
digitalWrite(MORSEPIN, MORSEOFF);
|
||||||
|
delay(250);
|
||||||
|
--count;
|
||||||
|
}
|
||||||
|
delay(2000);
|
||||||
|
}
|
||||||
|
/// is ready LED on
|
||||||
|
bool readyLEDActive = false;
|
||||||
|
|
||||||
|
/// main loop
|
||||||
|
void loop() {
|
||||||
|
if (morseIP > -1) {
|
||||||
|
if (morseIP == 0) {
|
||||||
|
readyLEDActive = false;
|
||||||
|
// send long start indicator
|
||||||
|
digitalWrite(MORSEPIN, MORSEON);
|
||||||
|
delay(1000);
|
||||||
|
digitalWrite(MORSEPIN, MORSEOFF);
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
// morse localIP[morseIP]
|
||||||
|
char digit = localIP[morseIP];
|
||||||
|
if (digit == '.') {
|
||||||
|
digitalWrite(MORSEPIN, MORSEON);
|
||||||
|
delay(10);
|
||||||
|
digitalWrite(MORSEPIN, MORSEOFF);
|
||||||
|
// wait 1 sec
|
||||||
|
delay(1000);
|
||||||
|
} else if (digit == 0) {
|
||||||
|
morseIP = -1; // restart
|
||||||
|
// send long end indicator
|
||||||
|
for (uint8_t i = 0; i < 50; ++i) {
|
||||||
|
digitalWrite(MORSEPIN, MORSEON);
|
||||||
|
delay(30);
|
||||||
|
digitalWrite(MORSEPIN, MORSEOFF);
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
// wait 2 sec
|
||||||
|
delay(2000);
|
||||||
|
} else {
|
||||||
|
morseDigit(digit - '0');
|
||||||
|
}
|
||||||
|
++morseIP;
|
||||||
|
} else if (!readyLEDActive) {
|
||||||
|
readyLEDActive = true;
|
||||||
|
digitalWrite(MORSEPIN, MORSEON);
|
||||||
|
}
|
||||||
|
|
||||||
|
server.handleClient();
|
||||||
|
wlanManager.loop();
|
||||||
|
|
||||||
|
if (run) {
|
||||||
|
digitalWrite(MORSEPIN, MORSEOFF);
|
||||||
|
runProgram();
|
||||||
|
digitalWrite(MORSEPIN, MORSEON);
|
||||||
|
run = false;
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include "wlanmanager.h"
|
||||||
|
|
||||||
|
#define _connectTimeout 5000
|
||||||
|
|
||||||
|
WLANManager::WLANManager() {
|
||||||
|
bzero(m_ssid, sizeof(m_ssid));
|
||||||
|
bzero(m_password, sizeof(m_password));
|
||||||
|
m_dnsServer = NULL;
|
||||||
|
m_isAccessPoint = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WLANManager::setSSID(const char* ssid, const char* password) {
|
||||||
|
strncpy(m_ssid, ssid, sizeof(m_ssid) - 1);
|
||||||
|
strncpy(m_password, password, sizeof(m_password) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WLANManager::loadConfigFromFile(const char* filename) {
|
||||||
|
File file = LittleFS.open(filename, "r"); // Open it
|
||||||
|
unsigned int size = file.size();
|
||||||
|
if (size == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char mode[20]={0};
|
||||||
|
char* config = reinterpret_cast<char*>(malloc(size + 1));
|
||||||
|
config[size] = 0;
|
||||||
|
file.read((unsigned char*)config, size);
|
||||||
|
file.close();
|
||||||
|
while (config[size - 1] == '\n') {
|
||||||
|
config[size - 1] = 0;
|
||||||
|
--size;
|
||||||
|
}
|
||||||
|
// format: "ssid;password;ap/client"
|
||||||
|
char* next = nextSemi(config);
|
||||||
|
strncpy(m_ssid, config, sizeof(m_ssid)-1);
|
||||||
|
if (!next) return;
|
||||||
|
char* start = next;
|
||||||
|
next = nextSemi(start);
|
||||||
|
strncpy(m_password, start, sizeof(m_password)-1);
|
||||||
|
start = next;
|
||||||
|
next = nextSemi(start);
|
||||||
|
strncpy(mode, start, sizeof(mode)-1);
|
||||||
|
m_isAccessPoint = strcmp(mode, "ap") == 0;
|
||||||
|
free(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WLANManager::start() {
|
||||||
|
// try to connect to stored AP
|
||||||
|
if (m_isAccessPoint || !connectToAp(NULL, NULL, NULL)) {
|
||||||
|
// fire up own AP
|
||||||
|
openAP();
|
||||||
|
m_dnsServer = new DNSServer();
|
||||||
|
m_dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
|
||||||
|
m_dnsServer->start(53, "*", WiFi.softAPIP());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WLANManager::connectToAp(const char* ssid, const char* pass, std::function<void()> callback) {
|
||||||
|
m_isAccessPoint = false;
|
||||||
|
if (ssid == NULL) {
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
}
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ssid) {
|
||||||
|
WiFi.begin(ssid, pass);
|
||||||
|
} else {
|
||||||
|
if (WiFi.SSID().length()) {
|
||||||
|
ETS_UART_INTR_DISABLE();
|
||||||
|
wifi_station_disconnect();
|
||||||
|
ETS_UART_INTR_ENABLE();
|
||||||
|
WiFi.begin();
|
||||||
|
} else {
|
||||||
|
WiFi.begin(m_ssid, m_password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int connRes = waitForConnectResult();
|
||||||
|
if (connRes != WL_CONNECTED) {
|
||||||
|
WiFi.beginWPSConfig();
|
||||||
|
// should be connected at the end of WPS
|
||||||
|
connRes = waitForConnectResult();
|
||||||
|
}
|
||||||
|
if (connRes == WL_CONNECTED) {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
if (m_dnsServer) {
|
||||||
|
delete m_dnsServer;
|
||||||
|
m_dnsServer = NULL;
|
||||||
|
}
|
||||||
|
return connRes == WL_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t WLANManager::waitForConnectResult() {
|
||||||
|
uint32_t start = millis();
|
||||||
|
boolean keepConnecting = true;
|
||||||
|
uint8_t status;
|
||||||
|
while (keepConnecting) {
|
||||||
|
status = WiFi.status();
|
||||||
|
if (millis() > start + _connectTimeout) {
|
||||||
|
keepConnecting = false;
|
||||||
|
}
|
||||||
|
if (status == WL_CONNECTED || status == WL_CONNECT_FAILED) {
|
||||||
|
keepConnecting = false;
|
||||||
|
}
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WLANManager::openAP() {
|
||||||
|
m_isAccessPoint = true;
|
||||||
|
// do we have stored settings?
|
||||||
|
if (!WiFi.isConnected()) {
|
||||||
|
WiFi.persistent(false);
|
||||||
|
// disconnect sta, start ap
|
||||||
|
WiFi.disconnect(); // this alone is not enough to stop the autoconnecter
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.persistent(true);
|
||||||
|
} else {
|
||||||
|
// setup AP
|
||||||
|
WiFi.mode(WIFI_AP_STA);
|
||||||
|
}
|
||||||
|
Serial.println(m_ssid);
|
||||||
|
Serial.println(m_password);
|
||||||
|
WiFi.softAP(m_ssid, m_password);
|
||||||
|
}
|
||||||
|
|
||||||
|
char* WLANManager::nextSemi(char* start) {
|
||||||
|
char* semi = strchr(start, ';');
|
||||||
|
if (!semi) return NULL;
|
||||||
|
*semi = 0;
|
||||||
|
return semi + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WLANManager::isAccessPoint() const {
|
||||||
|
return m_isAccessPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WLANManager::save(const char* filename) {
|
||||||
|
String line = String(m_ssid) + ";" + m_password + ";" + (m_isAccessPoint ? "ap" : "client");
|
||||||
|
File file = LittleFS.open(filename, "w");
|
||||||
|
file.write(line.c_str(), line.length());
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* WLANManager::getSSID() const {
|
||||||
|
return m_ssid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WLANManager::loop() {
|
||||||
|
if (m_dnsServer) {
|
||||||
|
m_dnsServer->processNextRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t WLANManager::getIP() const {
|
||||||
|
if (m_isAccessPoint)
|
||||||
|
return WiFi.softAPIP();
|
||||||
|
return WiFi.localIP();
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WLANMANAGER_H_
|
||||||
|
#define WLANMANAGER_H_
|
||||||
|
#include <DNSServer.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \class WLANManager
|
||||||
|
* \brief manager for and connecting to WLAN
|
||||||
|
*/
|
||||||
|
class WLANManager {
|
||||||
|
public:
|
||||||
|
/// \brief constructor
|
||||||
|
WLANManager();
|
||||||
|
|
||||||
|
/// \brief set SSID and password
|
||||||
|
/// \param ssid SSID to set
|
||||||
|
/// \param password password to set
|
||||||
|
void setSSID(const char* ssid, const char* password);
|
||||||
|
|
||||||
|
/// \brief load config from given file
|
||||||
|
/// \param filename file name of config file
|
||||||
|
void loadConfigFromFile(const char* filename);
|
||||||
|
|
||||||
|
/// \brief start WLAN
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/// \brief set mode to access point (true) or client (false)
|
||||||
|
/// \param isAccessPoint true if we are an access point
|
||||||
|
void setAccessPoint(bool isAccessPoint);
|
||||||
|
|
||||||
|
/// \brief are we an access point?
|
||||||
|
/// \return true if we are
|
||||||
|
bool isAccessPoint() const;
|
||||||
|
|
||||||
|
/// \brief save configuration
|
||||||
|
/// \param filename filename of config file
|
||||||
|
void save(const char* filename);
|
||||||
|
|
||||||
|
/// \brief connect to an access point
|
||||||
|
/// \param ssid ssid of AP
|
||||||
|
/// \param pass password of AP
|
||||||
|
/// \param callbal function to be called if connected
|
||||||
|
/// \retval true on success
|
||||||
|
bool connectToAp(const char* ssid, const char* pass, std::function<void()> callback);
|
||||||
|
|
||||||
|
/// \brief get SSID
|
||||||
|
/// \return current SSID
|
||||||
|
const char* getSSID() const;
|
||||||
|
|
||||||
|
/// \brief open access point
|
||||||
|
void openAP();
|
||||||
|
|
||||||
|
/// \brief internal loop, need to be called from main loop
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
/// \brief get current IP
|
||||||
|
/// \return current IP
|
||||||
|
uint32_t getIP() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// \brief parse input and search for next semicolon
|
||||||
|
/// \param start start offset. Input string will be modified
|
||||||
|
/// \return pointer to char after semicolor or NULL if not found
|
||||||
|
char* nextSemi(char* start);
|
||||||
|
|
||||||
|
/// \brief wait for connection to access point
|
||||||
|
/// \return WLAN status
|
||||||
|
uint8_t waitForConnectResult();
|
||||||
|
|
||||||
|
/// pointer to our dns server
|
||||||
|
DNSServer* m_dnsServer;
|
||||||
|
|
||||||
|
/// are we an access point
|
||||||
|
bool m_isAccessPoint;
|
||||||
|
|
||||||
|
/// our ssid
|
||||||
|
char m_ssid[64];
|
||||||
|
|
||||||
|
// our password
|
||||||
|
char m_password[64];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WLANMANAGER_H_
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,435 @@
|
||||||
|
{
|
||||||
|
"board": {
|
||||||
|
"design_settings": {
|
||||||
|
"defaults": {
|
||||||
|
"board_outline_line_width": 0.049999999999999996,
|
||||||
|
"copper_line_width": 0.19999999999999998,
|
||||||
|
"copper_text_italic": false,
|
||||||
|
"copper_text_size_h": 1.5,
|
||||||
|
"copper_text_size_v": 1.5,
|
||||||
|
"copper_text_thickness": 0.3,
|
||||||
|
"copper_text_upright": false,
|
||||||
|
"courtyard_line_width": 0.049999999999999996,
|
||||||
|
"dimension_precision": 4,
|
||||||
|
"dimension_units": 3,
|
||||||
|
"dimensions": {
|
||||||
|
"arrow_length": 1270000,
|
||||||
|
"extension_offset": 500000,
|
||||||
|
"keep_text_aligned": true,
|
||||||
|
"suppress_zeroes": false,
|
||||||
|
"text_position": 0,
|
||||||
|
"units_format": 1
|
||||||
|
},
|
||||||
|
"fab_line_width": 0.09999999999999999,
|
||||||
|
"fab_text_italic": false,
|
||||||
|
"fab_text_size_h": 1.0,
|
||||||
|
"fab_text_size_v": 1.0,
|
||||||
|
"fab_text_thickness": 0.15,
|
||||||
|
"fab_text_upright": false,
|
||||||
|
"other_line_width": 0.09999999999999999,
|
||||||
|
"other_text_italic": false,
|
||||||
|
"other_text_size_h": 1.0,
|
||||||
|
"other_text_size_v": 1.0,
|
||||||
|
"other_text_thickness": 0.15,
|
||||||
|
"other_text_upright": false,
|
||||||
|
"pads": {
|
||||||
|
"drill": 0.762,
|
||||||
|
"height": 1.524,
|
||||||
|
"width": 1.524
|
||||||
|
},
|
||||||
|
"silk_line_width": 0.12,
|
||||||
|
"silk_text_italic": false,
|
||||||
|
"silk_text_size_h": 1.0,
|
||||||
|
"silk_text_size_v": 1.0,
|
||||||
|
"silk_text_thickness": 0.15,
|
||||||
|
"silk_text_upright": false,
|
||||||
|
"zones": {
|
||||||
|
"45_degree_only": false,
|
||||||
|
"min_clearance": 0.508
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"diff_pair_dimensions": [
|
||||||
|
{
|
||||||
|
"gap": 0.0,
|
||||||
|
"via_gap": 0.0,
|
||||||
|
"width": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"drc_exclusions": [],
|
||||||
|
"meta": {
|
||||||
|
"filename": "board_design_settings.json",
|
||||||
|
"version": 2
|
||||||
|
},
|
||||||
|
"rule_severities": {
|
||||||
|
"annular_width": "error",
|
||||||
|
"clearance": "error",
|
||||||
|
"copper_edge_clearance": "error",
|
||||||
|
"courtyards_overlap": "error",
|
||||||
|
"diff_pair_gap_out_of_range": "error",
|
||||||
|
"diff_pair_uncoupled_length_too_long": "error",
|
||||||
|
"drill_out_of_range": "error",
|
||||||
|
"duplicate_footprints": "warning",
|
||||||
|
"extra_footprint": "warning",
|
||||||
|
"footprint_type_mismatch": "error",
|
||||||
|
"hole_clearance": "error",
|
||||||
|
"hole_near_hole": "error",
|
||||||
|
"invalid_outline": "error",
|
||||||
|
"item_on_disabled_layer": "error",
|
||||||
|
"items_not_allowed": "error",
|
||||||
|
"length_out_of_range": "error",
|
||||||
|
"malformed_courtyard": "error",
|
||||||
|
"microvia_drill_out_of_range": "error",
|
||||||
|
"missing_courtyard": "ignore",
|
||||||
|
"missing_footprint": "warning",
|
||||||
|
"net_conflict": "warning",
|
||||||
|
"npth_inside_courtyard": "ignore",
|
||||||
|
"padstack": "error",
|
||||||
|
"pth_inside_courtyard": "ignore",
|
||||||
|
"shorting_items": "error",
|
||||||
|
"silk_over_copper": "warning",
|
||||||
|
"silk_overlap": "warning",
|
||||||
|
"skew_out_of_range": "error",
|
||||||
|
"through_hole_pad_without_hole": "error",
|
||||||
|
"too_many_vias": "error",
|
||||||
|
"track_dangling": "warning",
|
||||||
|
"track_width": "error",
|
||||||
|
"tracks_crossing": "error",
|
||||||
|
"unconnected_items": "error",
|
||||||
|
"unresolved_variable": "error",
|
||||||
|
"via_dangling": "warning",
|
||||||
|
"zone_has_empty_net": "error",
|
||||||
|
"zones_intersect": "error"
|
||||||
|
},
|
||||||
|
"rule_severitieslegacy_courtyards_overlap": true,
|
||||||
|
"rule_severitieslegacy_no_courtyard_defined": false,
|
||||||
|
"rules": {
|
||||||
|
"allow_blind_buried_vias": false,
|
||||||
|
"allow_microvias": false,
|
||||||
|
"max_error": 0.005,
|
||||||
|
"min_clearance": 0.0,
|
||||||
|
"min_copper_edge_clearance": 0.0,
|
||||||
|
"min_hole_clearance": 0.25,
|
||||||
|
"min_hole_to_hole": 0.25,
|
||||||
|
"min_microvia_diameter": 0.19999999999999998,
|
||||||
|
"min_microvia_drill": 0.09999999999999999,
|
||||||
|
"min_silk_clearance": 0.0,
|
||||||
|
"min_through_hole_diameter": 0.3,
|
||||||
|
"min_track_width": 0.19999999999999998,
|
||||||
|
"min_via_annular_width": 0.049999999999999996,
|
||||||
|
"min_via_diameter": 0.39999999999999997,
|
||||||
|
"use_height_for_length_calcs": true
|
||||||
|
},
|
||||||
|
"track_widths": [
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"via_dimensions": [
|
||||||
|
{
|
||||||
|
"diameter": 0.0,
|
||||||
|
"drill": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"zones_allow_external_fillets": false,
|
||||||
|
"zones_use_no_outline": true
|
||||||
|
},
|
||||||
|
"layer_presets": []
|
||||||
|
},
|
||||||
|
"boards": [],
|
||||||
|
"cvpcb": {
|
||||||
|
"equivalence_files": []
|
||||||
|
},
|
||||||
|
"erc": {
|
||||||
|
"erc_exclusions": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"pin_map": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rule_severities": {
|
||||||
|
"bus_definition_conflict": "error",
|
||||||
|
"bus_entry_needed": "error",
|
||||||
|
"bus_label_syntax": "error",
|
||||||
|
"bus_to_bus_conflict": "error",
|
||||||
|
"bus_to_net_conflict": "error",
|
||||||
|
"different_unit_footprint": "error",
|
||||||
|
"different_unit_net": "error",
|
||||||
|
"duplicate_reference": "error",
|
||||||
|
"duplicate_sheet_names": "error",
|
||||||
|
"extra_units": "error",
|
||||||
|
"global_label_dangling": "warning",
|
||||||
|
"hier_label_mismatch": "error",
|
||||||
|
"label_dangling": "error",
|
||||||
|
"lib_symbol_issues": "warning",
|
||||||
|
"multiple_net_names": "warning",
|
||||||
|
"net_not_bus_member": "warning",
|
||||||
|
"no_connect_connected": "warning",
|
||||||
|
"no_connect_dangling": "warning",
|
||||||
|
"pin_not_connected": "error",
|
||||||
|
"pin_not_driven": "error",
|
||||||
|
"pin_to_pin": "warning",
|
||||||
|
"power_pin_not_driven": "error",
|
||||||
|
"similar_labels": "warning",
|
||||||
|
"unannotated": "error",
|
||||||
|
"unit_value_mismatch": "error",
|
||||||
|
"unresolved_variable": "error",
|
||||||
|
"wire_dangling": "error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"pinned_footprint_libs": [],
|
||||||
|
"pinned_symbol_libs": []
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"filename": "electronics.kicad_pro",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
"net_settings": {
|
||||||
|
"classes": [
|
||||||
|
{
|
||||||
|
"bus_width": 12.0,
|
||||||
|
"clearance": 0.2,
|
||||||
|
"diff_pair_gap": 0.25,
|
||||||
|
"diff_pair_via_gap": 0.25,
|
||||||
|
"diff_pair_width": 0.2,
|
||||||
|
"line_style": 0,
|
||||||
|
"microvia_diameter": 0.3,
|
||||||
|
"microvia_drill": 0.1,
|
||||||
|
"name": "Default",
|
||||||
|
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"track_width": 1.0,
|
||||||
|
"via_diameter": 0.8,
|
||||||
|
"via_drill": 0.4,
|
||||||
|
"wire_width": 6.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"version": 2
|
||||||
|
},
|
||||||
|
"net_colors": null
|
||||||
|
},
|
||||||
|
"pcbnew": {
|
||||||
|
"last_paths": {
|
||||||
|
"gencad": "",
|
||||||
|
"idf": "",
|
||||||
|
"netlist": "",
|
||||||
|
"specctra_dsn": "",
|
||||||
|
"step": "",
|
||||||
|
"vrml": ""
|
||||||
|
},
|
||||||
|
"page_layout_descr_file": ""
|
||||||
|
},
|
||||||
|
"schematic": {
|
||||||
|
"annotate_start_num": 0,
|
||||||
|
"drawing": {
|
||||||
|
"default_line_thickness": 6.0,
|
||||||
|
"default_text_size": 50.0,
|
||||||
|
"field_names": [],
|
||||||
|
"intersheets_ref_own_page": false,
|
||||||
|
"intersheets_ref_prefix": "",
|
||||||
|
"intersheets_ref_short": false,
|
||||||
|
"intersheets_ref_show": false,
|
||||||
|
"intersheets_ref_suffix": "",
|
||||||
|
"junction_size_choice": 3,
|
||||||
|
"label_size_ratio": 0.25,
|
||||||
|
"pin_symbol_size": 0.0,
|
||||||
|
"text_offset_ratio": 0.08
|
||||||
|
},
|
||||||
|
"legacy_lib_dir": "",
|
||||||
|
"legacy_lib_list": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
"net_format_name": "",
|
||||||
|
"ngspice": {
|
||||||
|
"fix_include_paths": true,
|
||||||
|
"fix_passive_vals": false,
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"model_mode": 0,
|
||||||
|
"workbook_filename": ""
|
||||||
|
},
|
||||||
|
"page_layout_descr_file": "${KICAD6_TEMPLATE_DIR}/pagelayout_default.kicad_wks",
|
||||||
|
"plot_directory": "",
|
||||||
|
"spice_adjust_passive_values": false,
|
||||||
|
"spice_external_command": "spice \"%I\"",
|
||||||
|
"subpart_first_id": 65,
|
||||||
|
"subpart_id_separator": 0
|
||||||
|
},
|
||||||
|
"sheets": [
|
||||||
|
[
|
||||||
|
"102cb741-e9ae-4781-bb1b-b26fba217145",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"text_variables": {}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,249 @@
|
||||||
|
update=Mo 13 Jul 2020 19:09:55 CEST
|
||||||
|
version=1
|
||||||
|
last_client=kicad
|
||||||
|
[general]
|
||||||
|
version=1
|
||||||
|
RootSch=
|
||||||
|
BoardNm=
|
||||||
|
[cvpcb]
|
||||||
|
version=1
|
||||||
|
NetIExt=net
|
||||||
|
[eeschema]
|
||||||
|
version=1
|
||||||
|
LibDir=
|
||||||
|
[eeschema/libraries]
|
||||||
|
[schematic_editor]
|
||||||
|
version=1
|
||||||
|
PageLayoutDescrFile=empty.kicad_wks
|
||||||
|
PlotDirectoryName=
|
||||||
|
SubpartIdSeparator=0
|
||||||
|
SubpartFirstId=65
|
||||||
|
NetFmtName=
|
||||||
|
SpiceAjustPassiveValues=0
|
||||||
|
LabSize=50
|
||||||
|
ERC_TestSimilarLabels=1
|
||||||
|
[pcbnew]
|
||||||
|
version=1
|
||||||
|
PageLayoutDescrFile=
|
||||||
|
LastNetListRead=
|
||||||
|
CopperLayerCount=2
|
||||||
|
BoardThickness=1.6
|
||||||
|
AllowMicroVias=0
|
||||||
|
AllowBlindVias=0
|
||||||
|
RequireCourtyardDefinitions=0
|
||||||
|
ProhibitOverlappingCourtyards=1
|
||||||
|
MinTrackWidth=0.2
|
||||||
|
MinViaDiameter=0.4
|
||||||
|
MinViaDrill=0.3
|
||||||
|
MinMicroViaDiameter=0.2
|
||||||
|
MinMicroViaDrill=0.09999999999999999
|
||||||
|
MinHoleToHole=0.25
|
||||||
|
TrackWidth1=0.25
|
||||||
|
TrackWidth2=1
|
||||||
|
ViaDiameter1=0.8
|
||||||
|
ViaDrill1=0.4
|
||||||
|
dPairWidth1=0.2
|
||||||
|
dPairGap1=0.25
|
||||||
|
dPairViaGap1=0.25
|
||||||
|
SilkLineWidth=0.12
|
||||||
|
SilkTextSizeV=1
|
||||||
|
SilkTextSizeH=1
|
||||||
|
SilkTextSizeThickness=0.15
|
||||||
|
SilkTextItalic=0
|
||||||
|
SilkTextUpright=1
|
||||||
|
CopperLineWidth=0.2
|
||||||
|
CopperTextSizeV=1.5
|
||||||
|
CopperTextSizeH=1.5
|
||||||
|
CopperTextThickness=0.3
|
||||||
|
CopperTextItalic=0
|
||||||
|
CopperTextUpright=1
|
||||||
|
EdgeCutLineWidth=0.05
|
||||||
|
CourtyardLineWidth=0.05
|
||||||
|
OthersLineWidth=0.15
|
||||||
|
OthersTextSizeV=1
|
||||||
|
OthersTextSizeH=1
|
||||||
|
OthersTextSizeThickness=0.15
|
||||||
|
OthersTextItalic=0
|
||||||
|
OthersTextUpright=1
|
||||||
|
SolderMaskClearance=0.051
|
||||||
|
SolderMaskMinWidth=0.25
|
||||||
|
SolderPasteClearance=0
|
||||||
|
SolderPasteRatio=-0
|
||||||
|
[pcbnew/Layer.F.Cu]
|
||||||
|
Name=F.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.In1.Cu]
|
||||||
|
Name=In1.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In2.Cu]
|
||||||
|
Name=In2.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In3.Cu]
|
||||||
|
Name=In3.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In4.Cu]
|
||||||
|
Name=In4.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In5.Cu]
|
||||||
|
Name=In5.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In6.Cu]
|
||||||
|
Name=In6.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In7.Cu]
|
||||||
|
Name=In7.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In8.Cu]
|
||||||
|
Name=In8.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In9.Cu]
|
||||||
|
Name=In9.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In10.Cu]
|
||||||
|
Name=In10.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In11.Cu]
|
||||||
|
Name=In11.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In12.Cu]
|
||||||
|
Name=In12.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In13.Cu]
|
||||||
|
Name=In13.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In14.Cu]
|
||||||
|
Name=In14.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In15.Cu]
|
||||||
|
Name=In15.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In16.Cu]
|
||||||
|
Name=In16.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In17.Cu]
|
||||||
|
Name=In17.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In18.Cu]
|
||||||
|
Name=In18.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In19.Cu]
|
||||||
|
Name=In19.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In20.Cu]
|
||||||
|
Name=In20.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In21.Cu]
|
||||||
|
Name=In21.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In22.Cu]
|
||||||
|
Name=In22.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In23.Cu]
|
||||||
|
Name=In23.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In24.Cu]
|
||||||
|
Name=In24.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In25.Cu]
|
||||||
|
Name=In25.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In26.Cu]
|
||||||
|
Name=In26.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In27.Cu]
|
||||||
|
Name=In27.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In28.Cu]
|
||||||
|
Name=In28.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In29.Cu]
|
||||||
|
Name=In29.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.In30.Cu]
|
||||||
|
Name=In30.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Layer.B.Cu]
|
||||||
|
Name=B.Cu
|
||||||
|
Type=0
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.B.Adhes]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.F.Adhes]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.B.Paste]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.F.Paste]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.B.SilkS]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.F.SilkS]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.B.Mask]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.F.Mask]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.Dwgs.User]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.Cmts.User]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.Eco1.User]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.Eco2.User]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.Edge.Cuts]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.Margin]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.B.CrtYd]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.F.CrtYd]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.B.Fab]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.F.Fab]
|
||||||
|
Enabled=1
|
||||||
|
[pcbnew/Layer.Rescue]
|
||||||
|
Enabled=0
|
||||||
|
[pcbnew/Netclasses]
|
||||||
|
[pcbnew/Netclasses/Default]
|
||||||
|
Name=Default
|
||||||
|
Clearance=0.2
|
||||||
|
TrackWidth=0.25
|
||||||
|
ViaDiameter=0.8
|
||||||
|
ViaDrill=0.4
|
||||||
|
uViaDiameter=0.3
|
||||||
|
uViaDrill=0.1
|
||||||
|
dPairWidth=0.2
|
||||||
|
dPairGap=0.25
|
||||||
|
dPairViaGap=0.25
|
Binary file not shown.
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"invalidpassword": "SSID or password wrong"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"generic": {
|
||||||
|
"save": "Save",
|
||||||
|
"savesuccess": "Saved successfully"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"main": {
|
||||||
|
"edittiming": "Edit Timing",
|
||||||
|
"save": "Just save",
|
||||||
|
"start": "Start time",
|
||||||
|
"trigger": "Save and Go!",
|
||||||
|
"needpause": "IR Triggering needs $1 ms pause until next action"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"mapping": {
|
||||||
|
"edittitle": "Edit mapping",
|
||||||
|
"pin": "Pin",
|
||||||
|
"type": "Type",
|
||||||
|
"inverted": "Inverted",
|
||||||
|
"digitalout": "digital output",
|
||||||
|
"ircanon": "Canon IR",
|
||||||
|
"irnikon": "Nikon IR",
|
||||||
|
"irsony": "Sony IR",
|
||||||
|
"notconn": "Not connected",
|
||||||
|
"trigger": "trigger signal"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"menu": {
|
||||||
|
"setupwifi": "Setup WiFi",
|
||||||
|
"setuppinmapping": "Setup Pin Mapping"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"update": {
|
||||||
|
"header": "Update Camera Trigger",
|
||||||
|
"fileselect": "select the new file:",
|
||||||
|
"submit": "do update"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"wifi": {
|
||||||
|
"accessmode": "Access mode",
|
||||||
|
"accesspoint": "Access point",
|
||||||
|
"client": "Client device",
|
||||||
|
"accessdetails": "Access details",
|
||||||
|
"rescan": "Rescan networks",
|
||||||
|
"channel": "Channel",
|
||||||
|
"ssid": "SSID",
|
||||||
|
"password": "Password",
|
||||||
|
"encryptionType": "Encryption",
|
||||||
|
"encryption": "yes",
|
||||||
|
"encryptionopen": "no"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
@labelColor: #222222;
|
||||||
|
@labelBorder: transparent;
|
||||||
|
@fontColor: #dedede;
|
||||||
|
@backgroundColor: #303030;
|
||||||
|
@headerBackground: #606060;
|
||||||
|
@headerBottom: rgb(19,19,19);
|
||||||
|
@boxBorderColor: rgb(80,80,80);
|
||||||
|
@boxShadowTLColor: rgb(100,100,100);
|
||||||
|
@boxShadowBRColor: rgb(0,0,0);
|
||||||
|
@boxBackgroundColor: #404040;
|
||||||
|
@loadingColor: rgba(255,255,255,0.25);
|
||||||
|
|
||||||
|
@buttonUnCheckedColorInner: #808080;
|
||||||
|
@buttonUnCheckedColorOuter: #606060;
|
||||||
|
@buttonCheckedColorInner: #839eff;
|
||||||
|
@buttonCheckedColorOuter: #334eb0;
|
||||||
|
@buttonShadowTLColor: rgb(150,150,150);
|
||||||
|
@buttonShadowBRColor: rgb(50,50,50);
|
||||||
|
|
||||||
|
@buttonUnCheckedColorInnerGreen: mix(@buttonUnCheckedColorInner, rgb(0,255,0), 95%);
|
||||||
|
@buttonUnCheckedColorOuterGreen: mix(@buttonUnCheckedColorOuter, rgb(0,255,0), 95%);
|
||||||
|
@buttonUnCheckedColorInnerRed: mix(@buttonUnCheckedColorInner, rgb(255,0,0), 95%);
|
||||||
|
@buttonUnCheckedColorOuterRed: mix(@buttonUnCheckedColorOuter, rgb(255,0,0), 95%);
|
||||||
|
|
||||||
|
|
||||||
|
@labelBackground: @buttonUnCheckedColorOuter;
|
||||||
|
@inputBorderColor:rgb(150,150,150);
|
||||||
|
@inputBorderColorActive:mix(@inputBorderColor, rgb(255,255,255),50%);
|
||||||
|
|
||||||
|
@errorColor: rgb(255,0,0);
|
|
@ -0,0 +1,114 @@
|
||||||
|
@import "colors.less";
|
||||||
|
@import "header.less";
|
||||||
|
@import "fform.less";
|
||||||
|
@import "mapping.less";
|
||||||
|
@import "main.less";
|
||||||
|
@import "wifi.less";
|
||||||
|
@import "update.less";
|
||||||
|
|
||||||
|
html,body {
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background:@backgroundColor;
|
||||||
|
color: @fontColor;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
.hsplit {
|
||||||
|
display: flex;
|
||||||
|
> div + div {
|
||||||
|
margin-left:40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.half {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
margin-left:auto;
|
||||||
|
margin-right:auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.none {
|
||||||
|
list-style-type: none;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.inline li {
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.errormsg {
|
||||||
|
color:#FF0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading:before {
|
||||||
|
display:inline-block;
|
||||||
|
content: ' ';
|
||||||
|
border-radius: 50%;
|
||||||
|
border: .5rem solid @loadingColor;
|
||||||
|
border-top-color: #000;
|
||||||
|
animation: spin 1s infinite linear;
|
||||||
|
width:16px;
|
||||||
|
height:16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading.smallani:before {
|
||||||
|
margin-left:-20px;
|
||||||
|
border-width: .3rem;
|
||||||
|
position:absolute;
|
||||||
|
width:8px;
|
||||||
|
height:8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, input, button {
|
||||||
|
outline:0;
|
||||||
|
}
|
||||||
|
::-moz-focus-inner {
|
||||||
|
border:0;
|
||||||
|
}
|
||||||
|
#main {
|
||||||
|
position:relative;
|
||||||
|
padding:10px;
|
||||||
|
}
|
||||||
|
#main {
|
||||||
|
h1, h2, h3 {
|
||||||
|
font-weight:500;
|
||||||
|
margin-left:10px;
|
||||||
|
margin-right:10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#mapping {
|
||||||
|
td {
|
||||||
|
vertical-align:middle;
|
||||||
|
}
|
||||||
|
input[type=text] {
|
||||||
|
margin-top:0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color:inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 400px) {
|
||||||
|
#main {
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
.fform {
|
||||||
|
min-width:320px;
|
||||||
|
background-color: @boxBackgroundColor;
|
||||||
|
box-shadow: -5px -5px 30px @boxShadowTLColor, 5px 5px 30px @boxShadowBRColor;
|
||||||
|
border-radius:20px;
|
||||||
|
border:1px solid @boxBorderColor;
|
||||||
|
margin:10px;
|
||||||
|
padding:10px;
|
||||||
|
position:relative;
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
&:before {
|
||||||
|
width:25px;
|
||||||
|
height:25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input[type=text] + label,
|
||||||
|
input[type=password] + label {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
margin: ~"calc(-1.85em + 1px) 1em 0";
|
||||||
|
&:before {
|
||||||
|
content: attr(placeholder);
|
||||||
|
display: inline-block;
|
||||||
|
color: @labelColor;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition-property: transform, color, font-size;
|
||||||
|
transition-duration: 0.2s;
|
||||||
|
transition-timing-function: ease-out;
|
||||||
|
transform-origin: left center;
|
||||||
|
border:1px solid @labelBorder;
|
||||||
|
border-bottom:0;
|
||||||
|
padding:3px;
|
||||||
|
border-radius:5px 5px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input,button,.button,select {
|
||||||
|
box-shadow: -3px -3px 10px @buttonShadowTLColor, 3px 3px 10px @buttonShadowBRColor;
|
||||||
|
background: radial-gradient(@buttonUnCheckedColorInner 20%, @buttonUnCheckedColorOuter 100%);
|
||||||
|
border:1px solid @inputBorderColor;
|
||||||
|
border-radius:1em;
|
||||||
|
color: @labelColor;
|
||||||
|
transition: box-shadow .5s, border .5s;
|
||||||
|
&:hover, &:focus, &:focus-within {
|
||||||
|
box-shadow: none;
|
||||||
|
border:1px solid @inputBorderColorActive;
|
||||||
|
transition: box-shadow 0.5s, border .5s;
|
||||||
|
}
|
||||||
|
&:checked, &:active {
|
||||||
|
box-shadow: -5px -5px 13px @buttonShadowBRColor, 3px 3px 10px @buttonShadowTLColor;
|
||||||
|
transition: box-shadow 0.5s;
|
||||||
|
}
|
||||||
|
&.green {
|
||||||
|
background: radial-gradient(@buttonUnCheckedColorInnerGreen 20%, @buttonUnCheckedColorOuterGreen 100%);
|
||||||
|
}
|
||||||
|
&.red {
|
||||||
|
background: radial-gradient(@buttonUnCheckedColorInnerRed 20%, @buttonUnCheckedColorOuterRed 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input[type=radio], input[type=checkbox], select {
|
||||||
|
height: 2em;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
margin-top:-4px;
|
||||||
|
vertical-align:top;
|
||||||
|
padding:0 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=radio], input[type=checkbox] {
|
||||||
|
width:2em;
|
||||||
|
&:checked {
|
||||||
|
background: radial-gradient(@buttonCheckedColorInner 20%, @buttonCheckedColorOuter 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
& + label {
|
||||||
|
margin:0.5em 0 0 5px;
|
||||||
|
display:inline-block;
|
||||||
|
height:2em;
|
||||||
|
vertical-align:top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input[type=text], input[type=password], input[type=submit], button {
|
||||||
|
font-size: 100%;
|
||||||
|
margin: 1.3em 0 0;
|
||||||
|
padding: 0.2em 0.5em 0;
|
||||||
|
height: 2em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
input[type=text], input[type=password] {
|
||||||
|
&:active {
|
||||||
|
box-shadow:none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
cursor:pointer;
|
||||||
|
outline:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label + a, label + p {
|
||||||
|
margin-top:1.3em;
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
input[type=text], input[type=password], input[type=file], select, button {
|
||||||
|
&:focus,
|
||||||
|
&.active,
|
||||||
|
&:valid,
|
||||||
|
&.invalid,
|
||||||
|
&:disabled {
|
||||||
|
& + label:before {
|
||||||
|
transform: translateY(-2.0em) translateX(0.5em);
|
||||||
|
border-color: @inputBorderColor;
|
||||||
|
font-size:70%;
|
||||||
|
background: @labelBackground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.errormsg {
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.successmsg {
|
||||||
|
margin-top:1em;
|
||||||
|
color:#009900;
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.small, .small {
|
||||||
|
font-size: 75%;
|
||||||
|
input[type=checkbox] + label {
|
||||||
|
margin:0.6em 0 0;
|
||||||
|
}
|
||||||
|
input[type=text], input[type=password], input[type=file], select {
|
||||||
|
&:focus,
|
||||||
|
&.active,
|
||||||
|
&:valid,
|
||||||
|
&.invalid,
|
||||||
|
&:disabled {
|
||||||
|
& + label:before {
|
||||||
|
transform: translateY(-2.3em) translateX(0.5em);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
header {
|
||||||
|
padding:0px 1em 1.5em;
|
||||||
|
background: linear-gradient(0deg, @backgroundColor 0%, @headerBottom 1.0em, @headerBackground 1.0em 100%);
|
||||||
|
h1 {
|
||||||
|
display:inline-block;
|
||||||
|
margin: 0 20px 0 0;
|
||||||
|
}
|
||||||
|
#menu {
|
||||||
|
display:inline-block;
|
||||||
|
margin:0;
|
||||||
|
li+li{
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color:inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
&:focus, &:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
#trigger {
|
||||||
|
button.trigger {
|
||||||
|
height:1.5em;
|
||||||
|
margin-top:0;
|
||||||
|
text-align:center;
|
||||||
|
font-size:2em;
|
||||||
|
img {
|
||||||
|
margin-right:20px;
|
||||||
|
vertical-align:top;
|
||||||
|
height:1em;
|
||||||
|
}
|
||||||
|
align-items:center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#edittiming {
|
||||||
|
> div {
|
||||||
|
float:left;
|
||||||
|
margin-bottom:20px;
|
||||||
|
.outer {
|
||||||
|
transition: font-size .1s, opacity 0.1s 0.1s;
|
||||||
|
}
|
||||||
|
overflow:hidden;
|
||||||
|
.minmax {
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
right:20px;
|
||||||
|
width:18px;
|
||||||
|
height:18px;
|
||||||
|
border-radius:50%;
|
||||||
|
background: @buttonUnCheckedColorOuter;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
top:5px;
|
||||||
|
right:1px;
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
border-top: 8px solid @buttonCheckedColorInner;
|
||||||
|
border-left: 8px solid transparent;
|
||||||
|
border-right: 8px solid transparent;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
position:absolute;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
text-align:center;
|
||||||
|
margin:0;
|
||||||
|
font-weight:500;
|
||||||
|
}
|
||||||
|
input[type=text] {
|
||||||
|
width:80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.del {
|
||||||
|
width:40px;
|
||||||
|
}
|
||||||
|
.time {
|
||||||
|
white-space:nowrap;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
margin:10px -10px -10px;
|
||||||
|
border:0;
|
||||||
|
border-top:1px solid rgba(0,0,0,.3);
|
||||||
|
border-bottom:1px solid rgba(255,255,255,.3);
|
||||||
|
}
|
||||||
|
&.min {
|
||||||
|
.outer {
|
||||||
|
font-size:0;
|
||||||
|
opacity:0;
|
||||||
|
transition: opacity 0.1s, font-size 0.1s .1s;
|
||||||
|
}
|
||||||
|
.minmax:before{
|
||||||
|
transform: rotate(90deg);
|
||||||
|
transition:transform .2s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#mainview .error {
|
||||||
|
color:@errorColor;
|
||||||
|
margin:25px 10px;
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 800px) {
|
||||||
|
#trigger {
|
||||||
|
width:400px;
|
||||||
|
}
|
||||||
|
#edittiming > div {
|
||||||
|
width:400px;
|
||||||
|
.time br {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
#edittiming {
|
||||||
|
> div {
|
||||||
|
width:auto;
|
||||||
|
float:none;
|
||||||
|
.time {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
button.del {
|
||||||
|
display:block;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
#mapping {
|
||||||
|
max-width:500px;
|
||||||
|
.pin {
|
||||||
|
display:inline-block;
|
||||||
|
height:2em;
|
||||||
|
margin:0.5em 0 0;
|
||||||
|
vertical-align:top;
|
||||||
|
}
|
||||||
|
.mapping {
|
||||||
|
margin-bottom:0.5em;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
.overlay {
|
||||||
|
position:fixed;
|
||||||
|
z-index:128000;
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
|
width:100%;
|
||||||
|
height:100vh;
|
||||||
|
background:rgba(0,0,0,0.5);
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
margin: 12vh auto 0;
|
||||||
|
max-width:960px;
|
||||||
|
min-width:350px;
|
||||||
|
min-height:300px;
|
||||||
|
width:75%;
|
||||||
|
height:75vh;
|
||||||
|
background:#FFF;
|
||||||
|
border:1px solid #cacaca;
|
||||||
|
border-radius:5px;
|
||||||
|
box-shadow:0 0 10px #000;
|
||||||
|
.header {
|
||||||
|
padding:5px;
|
||||||
|
border-bottom:1px solid #cacaca;
|
||||||
|
h2 {
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
font-size:120%;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
span.close {
|
||||||
|
float:right;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
overflow:auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
#update {
|
||||||
|
> div {
|
||||||
|
width:400px;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display:block;
|
||||||
|
margin-bottom:10px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
#wifi {
|
||||||
|
margin-top:20px;
|
||||||
|
max-width:600px;
|
||||||
|
.hsplit {
|
||||||
|
>div + div {
|
||||||
|
width:400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
#wifi {
|
||||||
|
.hsplit {
|
||||||
|
display:block;
|
||||||
|
>div + div {
|
||||||
|
margin:0;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"name": "CameraTrigger",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"homepage": "https://contentnation.net/grumpydevelop/",
|
||||||
|
"author": {
|
||||||
|
"name" : "Sascha Nitsch",
|
||||||
|
"url" : "https://contentnation.net/grumpydevelop/"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "ContentNation",
|
||||||
|
"url": "https://contentnation.net/grumpydevelop/"
|
||||||
|
},
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jquery": "^3.5.16",
|
||||||
|
"grunt": "^1.5.3",
|
||||||
|
"grunt-cli": "^1.4.3",
|
||||||
|
"grunt-contrib-concat": "^2.1.0",
|
||||||
|
"grunt-contrib-copy": "^1.0.0",
|
||||||
|
"grunt-contrib-less": "^3.0.0",
|
||||||
|
"grunt-contrib-uglify": "^5.2.2",
|
||||||
|
"grunt-contrib-watch": "^1.1.0",
|
||||||
|
"grunt-json-merge": "^0.2.2",
|
||||||
|
"grunt-newer": "^1.3.0",
|
||||||
|
"grunt-ts": "^6.0.0-beta.22",
|
||||||
|
"typescript": "^4.9.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^6.2.1",
|
||||||
|
"@types/mustache": "^4.2.2",
|
||||||
|
"jquery": "^3.6.3",
|
||||||
|
"jquery-toast-plugin": "^1.3.2",
|
||||||
|
"mustache": "^4.2.0"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"defaults"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"wifimode": "ap",
|
||||||
|
"ssid": "CameraTrigger"
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"d0": ["Flash 1", "digitalout", false],
|
||||||
|
"d1": ["Valve 1", "digitalout", false],
|
||||||
|
"d2": ["Valve 2", "digitalout", false],
|
||||||
|
"d3": ["trigger", "trigger", false],
|
||||||
|
"d4": ["", "none", false],
|
||||||
|
"d5": ["Flash 2", "digitalout", false],
|
||||||
|
"d6": ["Camera", "irsony", false],
|
||||||
|
"d7": ["", "none", false]
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"message": "ok"
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
[
|
||||||
|
["abc", 70, 2, ""],
|
||||||
|
["def", 65, 3, "open"]
|
||||||
|
]
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"error": "invalidpassword"
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Camera Trigger</title>
|
||||||
|
<script src="jquery.min.js"></script>
|
||||||
|
<script src="jquery.toast.min.js"></script>
|
||||||
|
<script src="mustache.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="default.css" type="text/css" />
|
||||||
|
<link rel="stylesheet" href="jquery.toast.min.css" type="text/css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1><a href="#"><img src="cameratrigger_logo.png" alt="logo" height="24"/> Camera Trigger</a></h1>
|
||||||
|
<ul id="menu" class="none inline"></ul>
|
||||||
|
</header>
|
||||||
|
<div id="main">loading ...</div>
|
||||||
|
</body>
|
||||||
|
<script src="bootstrap.min.js"></script>
|
||||||
|
</html>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script id="tplMain" type="x-tmpl-mustache">
|
||||||
|
<form><div id="mainview">
|
||||||
|
<div class="fform" id="trigger">
|
||||||
|
<button class="green trigger"><img src="cameratrigger_logo.png" alt="logo"/> {{l.main.trigger}}</button>
|
||||||
|
<button class="green save"> {{l.main.save}}</button>
|
||||||
|
</div>
|
||||||
|
<div class="error"></div>
|
||||||
|
<h2>{{l.main.edittiming}}</h2>
|
||||||
|
<div id="edittiming">
|
||||||
|
{{#pinmapping}}
|
||||||
|
<div class="fform" data-pin="{{pin}}">
|
||||||
|
<a href="#" class="minmax button" data-collapse="{{pin}}"> </a>
|
||||||
|
<h3>{{name}}</h3>
|
||||||
|
<div class="outer"><div class="inner">
|
||||||
|
<button class="add green">+</button>
|
||||||
|
</div></div>
|
||||||
|
</div>
|
||||||
|
{{/pinmapping}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</script>
|
||||||
|
<script id="tplMainTime" type="x-tmpl-mustache">
|
||||||
|
<div class="time">{{l.main.start}}: <input type="text" name="start_{{name}}_{{index}}" value="0"/> ms {{#delta}}Δ <input type="text" name="delta_{{name}}_{{index}}" value="1"/> ms {{/delta}}<button class="del red">-</button></div>
|
||||||
|
<hr />
|
||||||
|
</script>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<script id="tplMenu" type="x-tmpl-mustache">
|
||||||
|
<li><a href="#wifi">{{l.menu.setupwifi}}</a></li>
|
||||||
|
<li><a href="#pinmapping">{{l.menu.setuppinmapping}}</a></li>
|
||||||
|
</script>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script id="tplPinMapping" type="x-tmpl-mustache">
|
||||||
|
<form class="fform" id="mapping"><div>
|
||||||
|
<h2>{{l.mapping.edittitle}}</h2>
|
||||||
|
{{#pinmapping}}
|
||||||
|
<div class="mapping">
|
||||||
|
<span class="pin">{{l.mapping.pin}} {{pin}}</span>
|
||||||
|
<span class="pin"><label for="type_{{pin}}">{{l.mapping.type}}</label> <select name="type_{{pin}}" id="type_{{pin}}">
|
||||||
|
<option value="none">{{l.mapping.notconn}}</option>
|
||||||
|
<option value="digitalout">{{l.mapping.digitalout}}</option>
|
||||||
|
<option value="ircanon">{{l.mapping.ircanon}}</option>
|
||||||
|
<option value="irnikon">{{l.mapping.irnikon}}</option>
|
||||||
|
<option value="irsony">{{l.mapping.irsony}}</option>
|
||||||
|
<option value="trigger">{{l.mapping.trigger}}</option>
|
||||||
|
<option value="none">{{l.mapping.notconn}}</option>
|
||||||
|
</select></span
|
||||||
|
<span><input type="checkbox" name="inverted_{{pin}}" id="inverted_{{pin}}"{{#inverted}} checked="checked"{{/inverted}}/><label for="inverted_{{pin}}">{{l.mapping.inverted}}</label></span
|
||||||
|
<span><input type="text" name="name_{{pin}}" value="{{name}}" /></span>
|
||||||
|
</div>
|
||||||
|
{{/pinmapping}}
|
||||||
|
<input type="submit" value="{{l.generic.save}}" />
|
||||||
|
</table>
|
||||||
|
</div></form>
|
||||||
|
</script>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script id="tplUpdate" type="x-tmpl-mustache">
|
||||||
|
<form action="api/upload" method="post" enctype="multipart/form-data" id="update"><div class="fform">
|
||||||
|
<h2>{{l.update.header}}</h2>
|
||||||
|
<label for="update">{{l.update.fileselect}}</label>
|
||||||
|
<input type="file" name="update" /><br />
|
||||||
|
<input type="submit" value="{{l.update.submit}}" class="green" />
|
||||||
|
</div></form>
|
||||||
|
</script>
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script id="tplWifi" type="x-tmpl-mustache">
|
||||||
|
<form action="api/wifi" method="post" class="fform" id="wifi">
|
||||||
|
<div class="hsplit">
|
||||||
|
<div>
|
||||||
|
<h2>{{l.wifi.accessmode}}</h2>
|
||||||
|
<div><input type="radio" name="mode" id="ap" value="ap"><label for="ap">{{l.wifi.accesspoint}}</label></div>
|
||||||
|
<div><input type="radio" name="mode" id="client" value="client"><label for="client">{{l.wifi.client}}</label></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>{{l.wifi.accessdetails}}</h2>
|
||||||
|
<div class="hidden" id="ssidscan">
|
||||||
|
<button class="smallani">{{l.wifi.rescan}}</button>
|
||||||
|
<ul class="small none"></ul>
|
||||||
|
</div>
|
||||||
|
<div><input type="text" name="ssid" id="ssid" value="{{ssid}}" /><label for="ssid" placeholder="{{l.wifi.ssid}}"></label></div>
|
||||||
|
<div><input type="password" name="password" id="password" value="" /><label for="password" placeholder="{{l.wifi.password}}"></label></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
<button id="save" class="smallani">{{l.generic.save}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</script>
|
||||||
|
<script id="tplWifiSSID" type="x-tmpl-mustache">
|
||||||
|
{{#ssids}}
|
||||||
|
<li><input type="radio" name="ssidlist" id="radio{{index}}" value="{{name}}"{{#active}} checked="checked"{{/active}}><label for="radio{{index}}">{{name}} {{quality}}% {{l.wifi.channel}} {{channel}} {{l.wifi.encryptionType}} {{encryption}}</label></li>
|
||||||
|
{{/ssids}}
|
||||||
|
</script>
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <reference path="cameratrigger.ts" />
|
||||||
|
|
||||||
|
class Api {
|
||||||
|
/**
|
||||||
|
* ajax get call
|
||||||
|
* @param {string} path url path to call
|
||||||
|
* @param {object} parameter parameters to pass to api call
|
||||||
|
* @param {object} context optional parameter for setting the context the success and error function will be called
|
||||||
|
* @param {function} success optional success callback function
|
||||||
|
* @param {function} error optional error callback function
|
||||||
|
*/
|
||||||
|
static get(path: string, parameter, context, success, error) {
|
||||||
|
jQuery.ajax({
|
||||||
|
url : "api" + (path[0] != '/' ? '/' : '') + path,
|
||||||
|
dataType : 'json',
|
||||||
|
type : "GET",
|
||||||
|
data : parameter,
|
||||||
|
success : success,
|
||||||
|
error : error,
|
||||||
|
context : context
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* brief ajax post call api
|
||||||
|
* @param {string} path url path to call
|
||||||
|
* @param {object} parameter parameters to pass to api call
|
||||||
|
* @param {object} context optional parameter for setting the context the success and error function will be called
|
||||||
|
* @param {function} success optional success callback function
|
||||||
|
* @param {function} error optional error callback function
|
||||||
|
*/
|
||||||
|
static post(path: string, parameter, context, success, error) {
|
||||||
|
jQuery.ajax({
|
||||||
|
url : "api" + (path[0] != '/' ? '/' : '') + path,
|
||||||
|
dataType : 'json',
|
||||||
|
type : "POST",
|
||||||
|
data : parameter,
|
||||||
|
processData: !(parameter instanceof FormData),
|
||||||
|
success : success,
|
||||||
|
error : error,
|
||||||
|
context : context,
|
||||||
|
contentType: parameter instanceof FormData ? false : "application/x-www-form-urlencoded; charset=UTF-8"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* brief ajax del call api
|
||||||
|
* @param {string} path url path to call
|
||||||
|
* @param {object} parameter parameters to pass to api call
|
||||||
|
* @param {object} context optional parameter for setting the context the success and error function will be called
|
||||||
|
* @param {function} success optional success callback function
|
||||||
|
* @param {function} error optional error callback function
|
||||||
|
*/
|
||||||
|
static del(path: string, context, success, error) {
|
||||||
|
jQuery.ajax({
|
||||||
|
url : "api" + (path[0] != '/' ? '/' : '') + path,
|
||||||
|
dataType : 'json',
|
||||||
|
type : "DELETE",
|
||||||
|
success : success,
|
||||||
|
error : error,
|
||||||
|
context : context
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <reference path="forminfo.ts" />
|
||||||
|
/// <reference path="loader.ts" />
|
||||||
|
/// <reference path="view.ts" />
|
||||||
|
/// <reference path="toast.d.ts" />
|
||||||
|
/// <reference path="api.ts" />
|
||||||
|
|
||||||
|
interface Wifi {
|
||||||
|
mode:string;
|
||||||
|
ssid:string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PinMappings = Record<string,[string, string, boolean]>;
|
||||||
|
|
||||||
|
class CameraTrigger {
|
||||||
|
public form:FormInfo;
|
||||||
|
public lang: any;
|
||||||
|
public loader: Loader;
|
||||||
|
public routing: any;
|
||||||
|
public oldhash: string;
|
||||||
|
public view: View;
|
||||||
|
public wifi: Wifi;
|
||||||
|
public pinMapping: PinMappings;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.routing = {
|
||||||
|
"": "MainView",
|
||||||
|
"#wifi": "WifiView",
|
||||||
|
"#pinmapping": "PinMappingView",
|
||||||
|
"#update": "UpdateView"
|
||||||
|
};
|
||||||
|
|
||||||
|
this.loader = new Loader(this);
|
||||||
|
this.loadConfig();
|
||||||
|
this.loader.template("base", this, this.start, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getVersion() : string {
|
||||||
|
return "beta1";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default error function
|
||||||
|
* - disables loading indicator
|
||||||
|
* - fills in fields with class errormsg
|
||||||
|
* - show error toaster
|
||||||
|
*/
|
||||||
|
public error(jqXHR, textStatus, errorThrown) {
|
||||||
|
var error="error";
|
||||||
|
if (jqXHR.responseJSON) {
|
||||||
|
error = jqXHR.responseJSON.error;
|
||||||
|
} else {
|
||||||
|
if (errorThrown in app.lang.error) {
|
||||||
|
error = app.lang.error[errorThrown];
|
||||||
|
} else {
|
||||||
|
error = errorThrown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.form) {
|
||||||
|
$(this.form.target).removeClass("loading");
|
||||||
|
$(this.form.target).find('.errormsg').text(error);
|
||||||
|
}
|
||||||
|
$.toast({
|
||||||
|
text: error,
|
||||||
|
position: 'top-right',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default success function
|
||||||
|
* - disables loading indicator
|
||||||
|
* - fills in fields with class successmsg
|
||||||
|
* - show toaster
|
||||||
|
*/
|
||||||
|
public success(jqXHR, textStatus, errorThrown) {
|
||||||
|
var message="ok";
|
||||||
|
if (jqXHR.responseJSON) {
|
||||||
|
message = jqXHR.responseJSON.message;
|
||||||
|
if (message in app.lang.message) {
|
||||||
|
message = app.lang.message[message];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.form) {
|
||||||
|
$(this.form.target).removeClass("loading");
|
||||||
|
$(this.form.target).find('.successmsg').text(message);
|
||||||
|
}
|
||||||
|
$.toast({
|
||||||
|
text: message,
|
||||||
|
position: 'top-right',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
loadConfig() {
|
||||||
|
Api.get("config.json", {}, this, this.applyConfig, this.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyConfig(config) {
|
||||||
|
// load language file
|
||||||
|
this.loader.addLang("en", "main", this, this.start, this.error);
|
||||||
|
this.wifi = {mode: config.wifimode, ssid: config.ssid};
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (!this.lang || $("#tplMenu").length == 0) {
|
||||||
|
// try again the next time
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// render menu
|
||||||
|
var template = $("#tplMenu").html();
|
||||||
|
var data:any = {};
|
||||||
|
data.l = this.lang;
|
||||||
|
var html = Mustache.render(template, data);
|
||||||
|
$("#menu").prepend(html);
|
||||||
|
$(window).on('hashchange', this, this.doRouting);
|
||||||
|
this.doRouting();
|
||||||
|
}
|
||||||
|
|
||||||
|
doRouting(event?: JQuery.TriggeredEvent) {
|
||||||
|
if (event && event.data) {
|
||||||
|
event.data.doRouting();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var hash = document.location.hash;
|
||||||
|
if (hash != this.oldhash && this.view) {
|
||||||
|
this.view.finish();
|
||||||
|
}
|
||||||
|
this. oldhash = hash;
|
||||||
|
var className = this.routing[hash];
|
||||||
|
if (className) {
|
||||||
|
this.newClass(className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create a new instance of given classname with optional construction parameter
|
||||||
|
* - loads class if not yet known, filename: js/lowerCaseClassName.min.js
|
||||||
|
* - creates instance of class
|
||||||
|
* @param {any} classname class name
|
||||||
|
* @param {object} parameter optional parameter for new instance
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
newClass(classname: any, parameter?: object) : any {
|
||||||
|
if (typeof classname === "object") {
|
||||||
|
let newclass: any = window[classname.name];
|
||||||
|
new newclass(this, classname.parameter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!window[classname]) {
|
||||||
|
this.loader.js(classname.toLowerCase(), this, this.newClass, {
|
||||||
|
name : classname,
|
||||||
|
parameter : parameter
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let newclass: any = window[classname];
|
||||||
|
new newclass(this, parameter);
|
||||||
|
};
|
||||||
|
|
||||||
|
getPinMapping(context: any, callback: any) {
|
||||||
|
Api.get("pinmapping.json",{}, {t:this, context:context, callback:callback}, this.gotPinMapping, this.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
gotPinMapping(this:any, data: any) {
|
||||||
|
var self = this.t;
|
||||||
|
var cbcontext = this.context;
|
||||||
|
var callback = this.callback;
|
||||||
|
self.pinMapping = data;
|
||||||
|
if (callback) {
|
||||||
|
callback.call(cbcontext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setPinMapping(data: PinMappings) {
|
||||||
|
this.pinMapping = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var app: CameraTrigger;
|
||||||
|
$(document).ready(function() {
|
||||||
|
app = new CameraTrigger();
|
||||||
|
});
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
class FormInfo {
|
||||||
|
public enctype: string;
|
||||||
|
public target: JQuery.Node;
|
||||||
|
public path: string;
|
||||||
|
public parentElement: JQuery<HTMLElement>;
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/// <reference path="cameratrigger.ts" />
|
||||||
|
|
||||||
|
// IE stuff
|
||||||
|
interface HTMLElement {
|
||||||
|
readyState: any;
|
||||||
|
onreadystatechange: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Loader {
|
||||||
|
private loading: object;
|
||||||
|
private app: CameraTrigger;
|
||||||
|
|
||||||
|
constructor(app: CameraTrigger) {
|
||||||
|
this.loading = {};
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load css
|
||||||
|
* @param {string} file file to load
|
||||||
|
* - checks for duplicates
|
||||||
|
* - file name: /css/ + file + .css?v=version
|
||||||
|
*/
|
||||||
|
css(file: string) {
|
||||||
|
if (!document.getElementById("css_" + file)) {
|
||||||
|
var link = document.createElement('link');
|
||||||
|
link.setAttribute("rel", "stylesheet");
|
||||||
|
link.setAttribute("type", "text/css");
|
||||||
|
link.setAttribute("id", "css_" + file);
|
||||||
|
if (file[0] === "/") {
|
||||||
|
link.setAttribute("href", file);
|
||||||
|
} else {
|
||||||
|
link.setAttribute("href", "css/" + file + ".css?v=" + this.app.getVersion());
|
||||||
|
}
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(link);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gerneic callback wrapper supporting App scope or optional other scope
|
||||||
|
* @param {object} scope to use (undefined or false uses App instance)
|
||||||
|
* @param {Function} funct function to call
|
||||||
|
* @param {object} parameter paramter for funct
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private callback(scope: object, funct: Function, parameter: object) {
|
||||||
|
if (!scope) {
|
||||||
|
funct.call(this, parameter);
|
||||||
|
} else {
|
||||||
|
funct.call(scope, parameter);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load javascript
|
||||||
|
* @param {string} file file to load
|
||||||
|
* @param {object} scope scope for callback
|
||||||
|
* @param {Function} callback callback function
|
||||||
|
* @param {object} parameter parameter for callback
|
||||||
|
* - checks for duplicates
|
||||||
|
* - file name: /js/ + file + .min.js?v=version
|
||||||
|
*/
|
||||||
|
js(file: string, scope?: object, callback?: Function, parameter?: object) : boolean {
|
||||||
|
if (this.loading[file]) {
|
||||||
|
this.loading[file].push([scope,callback, parameter]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!document.getElementById("script_" + file.replace("/","_"))) {
|
||||||
|
var script:HTMLElement = document.createElement('script');
|
||||||
|
if (file[0] === "/") {
|
||||||
|
script.setAttribute("src", file);
|
||||||
|
} else {
|
||||||
|
script.setAttribute("src", file + ".min.js?v=" + this.app.getVersion());
|
||||||
|
}
|
||||||
|
if (!callback) {
|
||||||
|
callback = function() {
|
||||||
|
script.setAttribute("id", "script_" + file);
|
||||||
|
// console.log("trigger", "scriptload." + file);
|
||||||
|
$(document).trigger("scriptload." + file);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.loading[file] = [[scope,callback, parameter]];
|
||||||
|
if (callback) {
|
||||||
|
if (script.readyState) { // IE
|
||||||
|
script.onreadystatechange = function() {
|
||||||
|
if (script.readyState === "loaded" || script.readyState === "complete") {
|
||||||
|
script.onreadystatechange = null;
|
||||||
|
script.setAttribute("id", "script_" + file.replace("/","_"));
|
||||||
|
this.callbackList(file);
|
||||||
|
delete this.loading[file];
|
||||||
|
}
|
||||||
|
}.bind(this);
|
||||||
|
} else { // Others
|
||||||
|
script.onload = function() {
|
||||||
|
script.setAttribute("id", "script_" + file.replace("/","_"));
|
||||||
|
this.callbackList(file);
|
||||||
|
delete this.loading[file];
|
||||||
|
}.bind(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(script);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
this.callback(scope, callback, parameter);
|
||||||
|
} else {
|
||||||
|
$(document).trigger("scriptload." + file);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calls multiple callsbacks if a file has been loaded
|
||||||
|
* @param {string} file file that has been loaded
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
callbackList(file: string) {
|
||||||
|
for (var i = 0; i < this.loading[file].length; ++i) {
|
||||||
|
var c = this.loading[file][i];
|
||||||
|
if (c[0]) {
|
||||||
|
this.callback(c[0], c[1], c[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(document).trigger("scriptload." + file);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add translation file to global translation object
|
||||||
|
* @param {string} language
|
||||||
|
* @param {string} group language group
|
||||||
|
* @param {object} scope scope for callback function
|
||||||
|
* @param {Function} callback callback function
|
||||||
|
* @param {object} parameter parameter for callback
|
||||||
|
*/
|
||||||
|
addLang(lang: string, group: string, scope?: object, callback?:Function, parameter?: object) {
|
||||||
|
if (this.app.lang && this.app.lang[group]) {
|
||||||
|
if (callback) {
|
||||||
|
this.callback(scope, callback, parameter);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jQuery.ajax({
|
||||||
|
url : lang + "_" + group + ".json?v=" + this.app.getVersion(),
|
||||||
|
dataType : 'json',
|
||||||
|
type : "GET",
|
||||||
|
data : parameter,
|
||||||
|
success : function(a) {
|
||||||
|
this.app.lang = jQuery.extend(this.app.lang, a);
|
||||||
|
if (callback) {
|
||||||
|
this.callback(scope, callback, parameter);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error : this.app.error,
|
||||||
|
context : this
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template(file: string, scope: object, callback: Function, param?: object) {
|
||||||
|
jQuery.ajax({
|
||||||
|
url: file+'.mst?v=' + this.app.getVersion(),
|
||||||
|
dataType: 'text',
|
||||||
|
context: this,
|
||||||
|
success: function(template) {
|
||||||
|
$("body").append(template);
|
||||||
|
if (callback) {
|
||||||
|
this.callback(scope, callback, param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <reference path="view.ts" />
|
||||||
|
/// <reference path="cameratrigger.ts" />
|
||||||
|
|
||||||
|
class MainView extends View {
|
||||||
|
|
||||||
|
constructor(app: CameraTrigger) {
|
||||||
|
super(app);
|
||||||
|
if (!this.app.pinMapping) {
|
||||||
|
this.app.getPinMapping(this, this.render);
|
||||||
|
} else {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var template = $("#tplMain").html();
|
||||||
|
var data:any = {};
|
||||||
|
data.l = this.app.lang;
|
||||||
|
var keys = Object.keys(this.app.pinMapping);
|
||||||
|
data.pinmapping = [];
|
||||||
|
keys.forEach(function(key){
|
||||||
|
var pin = this.app.pinMapping[key];
|
||||||
|
if (pin[1] !== "none" && pin[1] !== "trigger") {
|
||||||
|
data.pinmapping.push({pin: key, name: pin[0]});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var html = Mustache.render(template, data);
|
||||||
|
this.root.html(html);
|
||||||
|
this.root.find("button.add").on("click", this, this.addTime);
|
||||||
|
this.root.on("click","button.del", this, this.delTime);
|
||||||
|
this.root.find(".minmax").on("click", this, this.toggleExpansion);
|
||||||
|
this.root.find("#trigger button.trigger").on("click", this, this.trigger);
|
||||||
|
this.root.find("#trigger button.save").on("click", this, this.save);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTime(event: JQuery.TriggeredEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
var target = $(event.currentTarget);
|
||||||
|
var template = $("#tplMainTime").html();
|
||||||
|
var data:any = {};
|
||||||
|
var fform = target.parents(".fform");
|
||||||
|
data.name = fform.data("pin");
|
||||||
|
data.index = fform.find(".time").length;
|
||||||
|
data.delta = event.data.app.pinMapping[data.name][1]==="digitalout";
|
||||||
|
data.l = event.data.app.lang;
|
||||||
|
var html = Mustache.render(template, data);
|
||||||
|
target.before(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
delTime(event: JQuery.TriggeredEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
var target = $(event.currentTarget).parent();
|
||||||
|
target.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleExpansion(event: JQuery.TriggeredEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var form = $(event.currentTarget).parents("div.fform")
|
||||||
|
form.toggleClass("min");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger(event: JQuery.TriggeredEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.data.realTrigger(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
save(event: JQuery.TriggeredEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.data.realTrigger(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
realTrigger(alsoTrigger: boolean) {
|
||||||
|
// get list of active pins
|
||||||
|
var times={};
|
||||||
|
var inputs = this.root.find("form").serializeArray();
|
||||||
|
for (var i = 0 ; i < inputs.length; ++i) {
|
||||||
|
var tokens = inputs[i].name.split("_");
|
||||||
|
var type = tokens[0]
|
||||||
|
var pin = parseInt(tokens[1].substr(1));
|
||||||
|
var time = parseInt(inputs[i].value);
|
||||||
|
if (type === "delta") {
|
||||||
|
time += parseInt(inputs[i-1].value);
|
||||||
|
}
|
||||||
|
if (!times[time]) {
|
||||||
|
times[time] = [];
|
||||||
|
}
|
||||||
|
var start = type === "start";
|
||||||
|
if (this.app.pinMapping[tokens[1]][2]) // inverted
|
||||||
|
start = !start;
|
||||||
|
switch (this.app.pinMapping[tokens[1]][1]) { // type
|
||||||
|
case "digitalout":
|
||||||
|
times[time].push((start ? "E" : "D") + pin);
|
||||||
|
break;
|
||||||
|
case "ircanon":
|
||||||
|
times[time].push("C" + pin);
|
||||||
|
break;
|
||||||
|
case "irnikon":
|
||||||
|
times[time].push("N" + pin);
|
||||||
|
break;
|
||||||
|
case "irsony":
|
||||||
|
times[time].push("S" + pin);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var keys = Object.keys(times).sort(function(a,b){var A=parseInt(a);var B=parseInt(b);return A<B ? -1 : ((A==B) ? 0 : 1);});
|
||||||
|
var cmdString = "";
|
||||||
|
var curTime = 0;
|
||||||
|
var needDelay = 0;
|
||||||
|
this.root.find(".error").html("");
|
||||||
|
for (var j = 0; j < keys.length ; ++j) {
|
||||||
|
var diff = parseInt(keys[j]) - curTime;
|
||||||
|
if (needDelay > diff) {
|
||||||
|
var err = this.app.lang.main.needpause.replace("$1", needDelay);
|
||||||
|
$.toast({
|
||||||
|
text: err,
|
||||||
|
position: 'top-right',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
this.root.find(".error").html(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (diff>0) {
|
||||||
|
cmdString += "d" + diff + ";";
|
||||||
|
}
|
||||||
|
needDelay = 0;
|
||||||
|
times[keys[j]].forEach(function(a) {
|
||||||
|
if (a[0] === 'C')
|
||||||
|
needDelay = 10;
|
||||||
|
else if (a[0] === 'N')
|
||||||
|
needDelay = 136;
|
||||||
|
else if (a[0] === 'S')
|
||||||
|
needDelay = 39;
|
||||||
|
});
|
||||||
|
cmdString += times[keys[j]].join(";") + ";";
|
||||||
|
curTime = parseInt(keys[j]);
|
||||||
|
}
|
||||||
|
//check for trigger
|
||||||
|
keys = Object.keys(this.app.pinMapping)
|
||||||
|
for (var k = 0; k < keys.length ; ++k) {
|
||||||
|
if (this.app.pinMapping[keys[k]][1] === "trigger") {
|
||||||
|
cmdString += "T" + keys[k].substr(1) + ";";
|
||||||
|
}
|
||||||
|
console.log(this.app.pinMapping[keys[k]]);
|
||||||
|
}
|
||||||
|
Api.post("/run", {"cmd": cmdString, "trigger": alsoTrigger}, this, this.app.success, this.app.error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <reference path="view.ts" />
|
||||||
|
/// <reference path="cameratrigger.ts" />
|
||||||
|
class PinMappingView extends View {
|
||||||
|
public form: any;
|
||||||
|
constructor(app: CameraTrigger) {
|
||||||
|
super(app);
|
||||||
|
if (!this.app.pinMapping) {
|
||||||
|
this.app.getPinMapping(this, this.render);
|
||||||
|
} else {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {
|
||||||
|
this.root.find("input[type=submit]").off("click", this.saveEdit);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var template = $("#tplPinMapping").html();
|
||||||
|
var data:any = {};
|
||||||
|
data.l = this.app.lang;
|
||||||
|
data.pinmapping = [];
|
||||||
|
var keys = Object.keys(this.app.pinMapping);
|
||||||
|
keys.forEach(function(key){
|
||||||
|
var pin = this.app.pinMapping[key];
|
||||||
|
data.pinmapping.push({pin: key, name: pin[0], type: pin[1], inverted: pin[2]});
|
||||||
|
});
|
||||||
|
var html = Mustache.render(template, data);
|
||||||
|
this.root.html(html);
|
||||||
|
this.root.find("input[type=submit]").on("click", this, this.saveEdit);
|
||||||
|
// set select boxes
|
||||||
|
for (var i = 0; i < 8; ++i) {
|
||||||
|
this.root.find("select[name=type_d" + i + "]").val(this.app.pinMapping["d" + i][1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveEdit(event?: JQuery.TriggeredEvent) {
|
||||||
|
if (event && event.data) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.data.saveEdit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var data = {};
|
||||||
|
var keys = Object.keys(this.app.pinMapping);
|
||||||
|
var root = this.root;
|
||||||
|
keys.forEach(function(key){
|
||||||
|
var pin = this.app.pinMapping[key];
|
||||||
|
data[key] = [root.find("input[name=name_" + key + "]").val(), root.find("#type_" + key).val(), root.find("input[name=inverted_" + key + "]").prop("checked")===true];
|
||||||
|
});
|
||||||
|
Api.post("pinmapping", JSON.stringify(data), this, this.success, this.app.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
success(data: PinMappings) {
|
||||||
|
this.app.setPinMapping(data);
|
||||||
|
$.toast({
|
||||||
|
text: this.app.lang.generic.savesuccess,
|
||||||
|
position: 'top-right',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
interface JQueryStatic {
|
||||||
|
toast(options?: any, callback?: Function) : any;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <reference path="view.ts" />
|
||||||
|
/// <reference path="cameratrigger.ts" />
|
||||||
|
class UpdateView extends View {
|
||||||
|
public form: any;
|
||||||
|
constructor(app: CameraTrigger) {
|
||||||
|
super(app);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var template = $("#tplUpdate").html();
|
||||||
|
var data:any = {};
|
||||||
|
data.l = this.app.lang;
|
||||||
|
var html = Mustache.render(template, data);
|
||||||
|
this.root.html(html);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
class View {
|
||||||
|
protected app: CameraTrigger;
|
||||||
|
protected root:JQuery<HTMLElement>;
|
||||||
|
constructor(app: CameraTrigger) {
|
||||||
|
this.app = app;
|
||||||
|
app.view = this;
|
||||||
|
this.root = $("#main");
|
||||||
|
this.root.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Sascha Nitsch (@grumpydevelop@contentnation.net) https://contentnation.net/en/grumpydevelop
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <reference path="view.ts" />
|
||||||
|
/// <reference path="cameratrigger.ts" />
|
||||||
|
interface SSID {
|
||||||
|
name: string;
|
||||||
|
quality: number;
|
||||||
|
active: boolean;
|
||||||
|
channel: number;
|
||||||
|
encryption: string;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
class WifiView extends View {
|
||||||
|
private ssidlist: Array<SSID>;
|
||||||
|
public form: any;
|
||||||
|
constructor(app: CameraTrigger) {
|
||||||
|
super(app);
|
||||||
|
this.render();
|
||||||
|
this.ssidlist = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {
|
||||||
|
this.root.off("change", "input[name=mode]");
|
||||||
|
this.root.find("#ssidscan button").off("click");
|
||||||
|
this.root.find("#ssidscan ul").off("change", "input[name=ssidlist]");
|
||||||
|
this.root.find("#save").off("click");
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var template = $("#tplWifi").html();
|
||||||
|
var data:any = {};
|
||||||
|
data.l = this.app.lang;
|
||||||
|
data.ssid = this.app.wifi.ssid;
|
||||||
|
var html = Mustache.render(template, data);
|
||||||
|
this.root.html(html);
|
||||||
|
var active = $("#" + this.app.wifi.mode);
|
||||||
|
active.prop("checked", true);
|
||||||
|
this.root.on("change", "input[name=mode]", this, this.modechange);
|
||||||
|
this.root.find("#ssidscan button").on("click", this, this.loadSSIDList);
|
||||||
|
this.root.find("#save").on("click", this, this.save);
|
||||||
|
this.modechange();
|
||||||
|
}
|
||||||
|
|
||||||
|
modechange(event?: JQuery.TriggeredEvent) {
|
||||||
|
if (event && event.data) {
|
||||||
|
event.data.modechange();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mode = this.root.find("input[name=mode]:checked").val();
|
||||||
|
var sync = this.root.find("#ssidscan");
|
||||||
|
var ssid = this.root.find("#ssid");
|
||||||
|
if (mode == "client") {
|
||||||
|
// show sync button and optionally load first list
|
||||||
|
sync.removeClass("hidden");
|
||||||
|
ssid.attr("disabled","disabled");
|
||||||
|
if (!this.ssidlist || this.ssidlist.length == 0) {
|
||||||
|
this.loadSSIDList();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// hide sync button
|
||||||
|
sync.addClass("hidden");
|
||||||
|
ssid.removeAttr("disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSSIDList(event?: JQuery.TriggeredEvent) {
|
||||||
|
if (event && event.data) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.data.loadSSIDList()
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var button = this.root.find("#ssidscan button");
|
||||||
|
button.addClass("loading");
|
||||||
|
Api.get("ssid.json", {}, this, this.ssidListReceived, this.app.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssidListReceived(listdata) {
|
||||||
|
var i;
|
||||||
|
this.ssidlist = [];
|
||||||
|
var button = this.root.find("#ssidscan button");
|
||||||
|
button.removeClass("loading");
|
||||||
|
var existing = this.root.find("#ssid").val();
|
||||||
|
for (i=0; i < listdata.length; ++i) {
|
||||||
|
this.ssidlist.push({name:listdata[i][0], quality: -listdata[i][1], active: listdata[i][0] === existing, channel:listdata[i][2], encryption: this.app.lang.wifi["encryption" + listdata[i][3]], index:i});
|
||||||
|
}
|
||||||
|
var template = $("#tplWifiSSID").html();
|
||||||
|
var data:any = {};
|
||||||
|
data.l = this.app.lang;
|
||||||
|
data.ssids = this.ssidlist;
|
||||||
|
var html = Mustache.render(template, data);
|
||||||
|
var list = this.root.find("#ssidscan ul");
|
||||||
|
list.html(html);
|
||||||
|
list.on("change", "input[name=ssidlist]", this, this.selectSSID);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectSSID(event?: JQuery.TriggeredEvent) {
|
||||||
|
if (event && event.data) {
|
||||||
|
event.data.selectSSID();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var ssid = this.root.find("#ssidscan input[name=ssidlist]:checked").val();
|
||||||
|
this.root.find("#ssid").val(ssid);
|
||||||
|
}
|
||||||
|
|
||||||
|
save(event?: JQuery.TriggeredEvent) {
|
||||||
|
if (event && event.data) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.data.save();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var save = this.root.find("#save");
|
||||||
|
save.addClass("loading");
|
||||||
|
this.form = {target: save[0]};
|
||||||
|
var data = {
|
||||||
|
ssid: this.root.find("input[name=ssid]").val(),
|
||||||
|
mode: this.root.find("input[name=mode]:checked").val(),
|
||||||
|
password: this.root.find("input[name=password]").val()
|
||||||
|
};
|
||||||
|
Api.post("wifi",data, this, this.saved, this.app.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
saved(response: any) {
|
||||||
|
this.root.find("#save").removeClass("loading");
|
||||||
|
if (response.error) {
|
||||||
|
$.toast({
|
||||||
|
text: this.app.lang.error[response.error],
|
||||||
|
position: 'top-right',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$.toast({
|
||||||
|
text: this.app.lang.generic.savesuccess,
|
||||||
|
position: 'top-right',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
window.setTimeout(function(){document.location.href="http://" + response.redirect;}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue