Compare commits
27 Commits
65118fbc42
...
develop
Author | SHA1 | Date | |
---|---|---|---|
1bb358c961 | |||
12a8391cd7 | |||
b6dc04920a | |||
aca1736201 | |||
fdf986a61e | |||
5c0822b704 | |||
fad4f2c707 | |||
84530f76fd | |||
fa208858d9 | |||
6d452ecbc0 | |||
23fbddb055 | |||
fe2a209e44 | |||
82905a8cdd | |||
3751904cb4 | |||
bcf7625285 | |||
4a3e79f02e | |||
68e1073858 | |||
f73d45404f | |||
ecc7c46b8d | |||
0dd5937707 | |||
547080acf5 | |||
d3c699aefa | |||
a8d19cd6e1 | |||
38d48ab0e4 | |||
51bef05465 | |||
4eef69516e | |||
9175193b67 |
@ -170,3 +170,5 @@ PLS files, M3U files or podcast XML feeds are supported). |
|
|||||||
| `add_mapping=<ID>=<PATH>` | Adds a mapping between RFID card <ID> and path
|
| `add_mapping=<ID>=<PATH>` | Adds a mapping between RFID card <ID> and path
|
||||||
<PATH>. See `play` for valid path formats. |
|
<PATH>. See `play` for valid path formats. |
|
||||||
| `update` | Runs an update check. |
|
| `update` | Runs an update check. |
|
||||||
|
| `debug=<0|1>` | Enables / disables debug messages. This value is persisted across reboots. |
|
||||||
|
| `trace=<0|1>` | Enables / disables tracing messages. This value is also persisted across reboots. |
|
3
bin/update.manifest
Normal file
3
bin/update.manifest
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
VERSION=1
|
||||||
|
IMAGE_PATH=https://files.schle.nz/esmp3/firmware.bin
|
||||||
|
IMAGE_MD5=00000000000000000000000000000000
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -x
|
#set -x
|
||||||
#set -e
|
set -e
|
||||||
|
|
||||||
if ! git diff-index --quiet HEAD ; then
|
if ! git diff-index --quiet HEAD ; then
|
||||||
echo "Git isn't clean. Cant deploy."
|
echo "Git isn't clean. Cant deploy."
|
||||||
@ -27,7 +27,7 @@ done <<< "$vers"
|
|||||||
read -p "Version to generate: " VERSION
|
read -p "Version to generate: " VERSION
|
||||||
|
|
||||||
OTA_VERSION=`grep "VERSION=" bin/update.manifest | cut -d"=" -f2`
|
OTA_VERSION=`grep "VERSION=" bin/update.manifest | cut -d"=" -f2`
|
||||||
OTA_VERSION=$(( "$OTA_VERSION" + 1 ))
|
OTA_VERSION=$(( $OTA_VERSION + 1 ))
|
||||||
|
|
||||||
sed -i.bak "s/#define OTA_VERSION .*/#define OTA_VERSION $OTA_VERSION/" include/config.h include/config.sample.h
|
sed -i.bak "s/#define OTA_VERSION .*/#define OTA_VERSION $OTA_VERSION/" include/config.h include/config.sample.h
|
||||||
rm include/config.h.bak include/config.sample.h.bak
|
rm include/config.h.bak include/config.sample.h.bak
|
||||||
|
@ -16,8 +16,9 @@ public:
|
|||||||
virtual void seek(size_t position) = 0;
|
virtual void seek(size_t position) = 0;
|
||||||
virtual size_t size() = 0;
|
virtual size_t size() = 0;
|
||||||
virtual void close() = 0;
|
virtual void close() = 0;
|
||||||
virtual void skip_id3_tag() {};
|
|
||||||
virtual bool usable() = 0;
|
virtual bool usable() = 0;
|
||||||
|
virtual int peek(int offset) = 0;
|
||||||
|
void skip_id3_tag();
|
||||||
};
|
};
|
||||||
|
|
||||||
class SDDataSource : public DataSource {
|
class SDDataSource : public DataSource {
|
||||||
@ -32,8 +33,8 @@ public:
|
|||||||
void seek(size_t position);
|
void seek(size_t position);
|
||||||
size_t size();
|
size_t size();
|
||||||
void close();
|
void close();
|
||||||
void skip_id3_tag();
|
|
||||||
bool usable();
|
bool usable();
|
||||||
|
int peek(int offset=0);
|
||||||
};
|
};
|
||||||
|
|
||||||
class HTTPSDataSource : public DataSource {
|
class HTTPSDataSource : public DataSource {
|
||||||
@ -41,6 +42,8 @@ private:
|
|||||||
WiFiClient* _stream = NULL;
|
WiFiClient* _stream = NULL;
|
||||||
HTTPClientWrapper* _http = NULL;
|
HTTPClientWrapper* _http = NULL;
|
||||||
uint32_t _position;
|
uint32_t _position;
|
||||||
|
String _url;
|
||||||
|
void _init(String url, uint32_t offset);
|
||||||
public:
|
public:
|
||||||
HTTPSDataSource(String url, uint32_t offset=0);
|
HTTPSDataSource(String url, uint32_t offset=0);
|
||||||
~HTTPSDataSource();
|
~HTTPSDataSource();
|
||||||
@ -51,4 +54,5 @@ public:
|
|||||||
size_t size();
|
size_t size();
|
||||||
void close();
|
void close();
|
||||||
bool usable();
|
bool usable();
|
||||||
|
int peek(int offset=0);
|
||||||
};
|
};
|
||||||
|
@ -34,4 +34,5 @@ public:
|
|||||||
uint32_t getSize();
|
uint32_t getSize();
|
||||||
String readUntil(String sep);
|
String readUntil(String sep);
|
||||||
String readLine();
|
String readLine();
|
||||||
|
int peek(int offset=0);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <Preferences.h>
|
||||||
|
|
||||||
void wifi_connect();
|
void wifi_connect();
|
||||||
|
|
||||||
extern const uint8_t file_index_html_start[] asm("_binary_src_index_html_start");
|
extern bool debug_enabled;
|
||||||
|
extern bool trace_enabled;
|
||||||
|
extern Preferences prefs;
|
||||||
|
|
||||||
|
extern const uint8_t file_index_html_start[] asm("_binary_src_webinterface_index_html_gz_start");
|
||||||
|
extern const size_t file_index_html_size asm("_binary_src_webinterface_index_html_gz_size");
|
||||||
|
extern const uint8_t file_timezones_json_start[] asm("_binary_src_webinterface_timezones_json_gz_start");
|
||||||
|
extern const size_t file_timezones_json_size asm("_binary_src_webinterface_timezones_json_gz_size");
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
#include "main.h"
|
||||||
#include "http_client_wrapper.h"
|
#include "http_client_wrapper.h"
|
||||||
|
|
||||||
enum PlaylistPersistence {
|
enum PlaylistPersistence {
|
||||||
|
@ -11,30 +11,27 @@
|
|||||||
[platformio]
|
[platformio]
|
||||||
default_envs = esp32
|
default_envs = esp32
|
||||||
|
|
||||||
[extra]
|
[env]
|
||||||
lib_deps =
|
|
||||||
63 ; MFRC522
|
|
||||||
https://github.com/me-no-dev/ESPAsyncWebServer.git
|
|
||||||
ArduinoJSON
|
|
||||||
6691 ; TinyXML
|
|
||||||
|
|
||||||
[env:esp32]
|
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = esp-wrover-kit
|
board = esp-wrover-kit
|
||||||
framework = arduino
|
framework = arduino
|
||||||
upload_speed = 512000
|
upload_speed = 512000
|
||||||
build_flags=!./build_version.sh
|
lib_deps =
|
||||||
lib_deps = ${extra.lib_deps}
|
63 ; MFRC522
|
||||||
upload_port = /dev/cu.SLAB_USBtoUART
|
https://github.com/me-no-dev/ESPAsyncWebServer.git
|
||||||
|
64 ; ArduinoJSON
|
||||||
|
6691 ; TinyXML
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
board_build.embed_txtfiles = src/index.html
|
board_build.embed_files =
|
||||||
board_build.partitions = partitions.csv
|
src/webinterface/timezones.json.gz
|
||||||
|
src/webinterface/index.html.gz
|
||||||
|
;board_build.partitions = partitions.csv
|
||||||
;monitor_port = /dev/cu.wchusbserial1420
|
;monitor_port = /dev/cu.wchusbserial1420
|
||||||
|
extra_scripts =
|
||||||
|
post:tools/post_build.py
|
||||||
|
|
||||||
|
[env:esp32]
|
||||||
|
build_flags=!./build_version.sh
|
||||||
|
upload_port = /dev/cu.SLAB_USBtoUART
|
||||||
|
|
||||||
[env:deploy]
|
[env:deploy]
|
||||||
platform = espressif32
|
|
||||||
board = esp-wrover-kit
|
|
||||||
framework = arduino
|
|
||||||
lib_deps = ${extra.lib_deps}
|
|
||||||
board_build.embed_txtfiles = src/index.html
|
|
||||||
board_build.partitions = partitions.csv
|
|
||||||
|
@ -36,7 +36,6 @@ void Controller::register_http_server(HTTPServer* h) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Controller::loop() {
|
void Controller::loop() {
|
||||||
TRACE("Controller::loop()...\n");
|
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
if ((_last_rfid_scan_at < now - RFID_SCAN_INTERVAL) || (now < _last_rfid_scan_at)) {
|
if ((_last_rfid_scan_at < now - RFID_SCAN_INTERVAL) || (now < _last_rfid_scan_at)) {
|
||||||
_check_rfid();
|
_check_rfid();
|
||||||
@ -66,7 +65,6 @@ void Controller::loop() {
|
|||||||
} else {
|
} else {
|
||||||
_last_wifi_try_at = now;
|
_last_wifi_try_at = now;
|
||||||
}
|
}
|
||||||
TRACE("Controller::loop() done.\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Controller::_get_rfid_card_uid() {
|
uint32_t Controller::_get_rfid_card_uid() {
|
||||||
@ -85,7 +83,7 @@ uint32_t Controller::_get_rfid_card_uid() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Controller::_check_rfid() {
|
void Controller::_check_rfid() {
|
||||||
TRACE("check_rfid running...\n");
|
//TRACE("check_rfid running...\n");
|
||||||
MFRC522::StatusCode status;
|
MFRC522::StatusCode status;
|
||||||
if (_rfid_present) {
|
if (_rfid_present) {
|
||||||
byte buffer[2];
|
byte buffer[2];
|
||||||
@ -144,7 +142,8 @@ void Controller::_check_rfid() {
|
|||||||
if (time.tm_mon == 11) { // tm_mon is "months since january", so 11 means december.
|
if (time.tm_mon == 11) { // tm_mon is "months since january", so 11 means december.
|
||||||
pl->advent_shuffle(time.tm_mday);
|
pl->advent_shuffle(time.tm_mday);
|
||||||
} else {
|
} else {
|
||||||
// TODO
|
DEBUG("Album is in advent mode, but it isn't december (yet). Not playing.\n");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else if (data.indexOf("[random]") != -1 && pl->is_fresh()) {
|
} else if (data.indexOf("[random]") != -1 && pl->is_fresh()) {
|
||||||
pl->shuffle();
|
pl->shuffle();
|
||||||
@ -200,7 +199,7 @@ String Controller::_read_rfid_data() {
|
|||||||
case MFRC522::PICC_TYPE_MIFARE_4K: sectors = 40; break;
|
case MFRC522::PICC_TYPE_MIFARE_4K: sectors = 40; break;
|
||||||
default: INFO("Unknown PICC type %s\n", String(MFRC522::PICC_GetTypeName(type)).c_str());
|
default: INFO("Unknown PICC type %s\n", String(MFRC522::PICC_GetTypeName(type)).c_str());
|
||||||
}
|
}
|
||||||
|
sectors = 2; // Pretend we have only two sectors, so we read only sector #1.
|
||||||
int good_key_index = -1;
|
int good_key_index = -1;
|
||||||
for (uint8_t sector=1; sector<sectors; sector++) {
|
for (uint8_t sector=1; sector<sectors; sector++) {
|
||||||
uint8_t blocks = (sector < 32) ? 4 : 16;
|
uint8_t blocks = (sector < 32) ? 4 : 16;
|
||||||
@ -244,8 +243,6 @@ String Controller::_read_rfid_data() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Controller::_check_serial() {
|
void Controller::_check_serial() {
|
||||||
TRACE("check_serial running...\n");
|
|
||||||
|
|
||||||
if (Serial.available() > 0) {
|
if (Serial.available() > 0) {
|
||||||
char c = Serial.read();
|
char c = Serial.read();
|
||||||
Serial.printf("%c", c);
|
Serial.printf("%c", c);
|
||||||
@ -304,6 +301,24 @@ bool Controller::process_message(String cmd) {
|
|||||||
} else if (cmd.equals("update")) {
|
} else if (cmd.equals("update")) {
|
||||||
Updater::run();
|
Updater::run();
|
||||||
#endif
|
#endif
|
||||||
|
} else if (cmd.startsWith("trace=")) {
|
||||||
|
int val = cmd.substring(6).toInt();
|
||||||
|
if (val==0) {
|
||||||
|
trace_enabled = false;
|
||||||
|
prefs.putBool("trace_enabled", false);
|
||||||
|
} else if (val==1) {
|
||||||
|
trace_enabled = true;
|
||||||
|
prefs.putBool("trace_enabled", true);
|
||||||
|
}
|
||||||
|
} else if (cmd.startsWith("debug=")) {
|
||||||
|
int val = cmd.substring(6).toInt();
|
||||||
|
if (val==0) {
|
||||||
|
debug_enabled = false;
|
||||||
|
prefs.putBool("debug_enabled", false);
|
||||||
|
} else if (val==1) {
|
||||||
|
debug_enabled = true;
|
||||||
|
prefs.putBool("debug_enabled", true);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ERROR("Unknown command: %s\n", cmd.c_str());
|
ERROR("Unknown command: %s\n", cmd.c_str());
|
||||||
return false;
|
return false;
|
||||||
@ -332,8 +347,6 @@ void Controller::_execute_command_help() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Controller::_check_buttons() {
|
void Controller::_check_buttons() {
|
||||||
TRACE("check_buttons running...\n");
|
|
||||||
|
|
||||||
if (BTN_PREV() && _debounce_button(0)) {
|
if (BTN_PREV() && _debounce_button(0)) {
|
||||||
if (_state == NORMAL) {
|
if (_state == NORMAL) {
|
||||||
player->track_prev();
|
player->track_prev();
|
||||||
|
@ -1,5 +1,29 @@
|
|||||||
#include "data_sources.h"
|
#include "data_sources.h"
|
||||||
|
|
||||||
|
void DataSource::skip_id3_tag() {
|
||||||
|
if (peek(0)=='I' && peek(1)=='D' && peek(2)=='3') {
|
||||||
|
DEBUG("ID3 tag found\n");
|
||||||
|
// Skip ID3 tag marker
|
||||||
|
read(); read(); read();
|
||||||
|
// Skip ID3 tag version
|
||||||
|
read(); read();
|
||||||
|
byte tags = read();
|
||||||
|
bool footer_present = tags & 0x10;
|
||||||
|
DEBUG("ID3 footer found: %d\n", footer_present);
|
||||||
|
uint32_t offset = 0;
|
||||||
|
for (byte i=0; i<4; i++) {
|
||||||
|
offset <<= 7;
|
||||||
|
offset |= (0x7F & read());
|
||||||
|
}
|
||||||
|
offset += 10;
|
||||||
|
if (footer_present) offset += 10;
|
||||||
|
DEBUG("ID3 tag length is %d bytes.\n", offset);
|
||||||
|
seek(offset);
|
||||||
|
} else {
|
||||||
|
DEBUG("No ID3 tag found\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
////////////// SDDataSource //////////////
|
////////////// SDDataSource //////////////
|
||||||
SDDataSource::SDDataSource(String file) { _file = SD.open(file, "r"); }
|
SDDataSource::SDDataSource(String file) { _file = SD.open(file, "r"); }
|
||||||
SDDataSource::~SDDataSource() { if (_file) _file.close(); }
|
SDDataSource::~SDDataSource() { if (_file) _file.close(); }
|
||||||
@ -10,35 +34,24 @@ void SDDataSource::seek(size_t position) { _file.seek(position); }
|
|||||||
size_t SDDataSource::size() { return _file.size(); }
|
size_t SDDataSource::size() { return _file.size(); }
|
||||||
void SDDataSource::close() { _file.close(); }
|
void SDDataSource::close() { _file.close(); }
|
||||||
bool SDDataSource::usable() { return _file; }
|
bool SDDataSource::usable() { return _file; }
|
||||||
|
int SDDataSource::peek(int offset) {
|
||||||
void SDDataSource::skip_id3_tag() {
|
if (offset==0) return _file.peek();
|
||||||
uint32_t original_position = _file.position();
|
size_t start_position = _file.position();
|
||||||
uint32_t offset = 0;
|
_file.seek(start_position + offset);
|
||||||
if (_file.read()=='I' && _file.read()=='D' && _file.read()=='3') {
|
int result = _file.peek();
|
||||||
DEBUG("ID3 tag found\n");
|
_file.seek(start_position);
|
||||||
// Skip ID3 tag version
|
return result;
|
||||||
_file.read(); _file.read();
|
|
||||||
byte tags = _file.read();
|
|
||||||
bool footer_present = tags & 0x10;
|
|
||||||
DEBUG("ID3 footer found: %d\n", footer_present);
|
|
||||||
for (byte i=0; i<4; i++) {
|
|
||||||
offset <<= 7;
|
|
||||||
offset |= (0x7F & _file.read());
|
|
||||||
}
|
|
||||||
offset += 10;
|
|
||||||
if (footer_present) offset += 10;
|
|
||||||
DEBUG("ID3 tag length is %d bytes.\n", offset);
|
|
||||||
_file.seek(offset);
|
|
||||||
} else {
|
|
||||||
DEBUG("No ID3 tag found\n");
|
|
||||||
_file.seek(original_position);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
////////////// HTTPSDataSource //////////////
|
////////////// HTTPSDataSource //////////////
|
||||||
HTTPSDataSource::HTTPSDataSource(String url, uint32_t offset) {
|
HTTPSDataSource::HTTPSDataSource(String url, uint32_t offset) {
|
||||||
|
_url = url;
|
||||||
|
_init(url, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPSDataSource::_init(String url, uint32_t offset) {
|
||||||
|
_url = url;
|
||||||
_http = new HTTPClientWrapper();
|
_http = new HTTPClientWrapper();
|
||||||
if (!_http->get(url, offset)) return;
|
if (!_http->get(url, offset)) return;
|
||||||
_position = 0;
|
_position = 0;
|
||||||
@ -52,6 +65,7 @@ bool HTTPSDataSource::usable() { return _http; }
|
|||||||
size_t HTTPSDataSource::read(uint8_t* buf, size_t len) { size_t result = _http->read(buf, len); _position += result; return result; }
|
size_t HTTPSDataSource::read(uint8_t* buf, size_t len) { size_t result = _http->read(buf, len); _position += result; return result; }
|
||||||
int HTTPSDataSource::read() { int b = _http->read(); if (b>=0) _position++; return b; }
|
int HTTPSDataSource::read() { int b = _http->read(); if (b>=0) _position++; return b; }
|
||||||
size_t HTTPSDataSource::position() { return _position; }
|
size_t HTTPSDataSource::position() { return _position; }
|
||||||
void HTTPSDataSource::seek(size_t position) { return; /* TODO */ }
|
void HTTPSDataSource::seek(size_t position) { _http->close(); delete _http; _init(_url, position); }
|
||||||
size_t HTTPSDataSource::size() { return _http->getSize(); }
|
size_t HTTPSDataSource::size() { return _http->getSize(); }
|
||||||
void HTTPSDataSource::close() { _http->close(); }
|
void HTTPSDataSource::close() { _http->close(); }
|
||||||
|
int HTTPSDataSource::peek(int offset) { return _http->peek(offset); }
|
||||||
|
@ -73,6 +73,9 @@ bool HTTPClientWrapper::_request(String method, String url, uint32_t offset, uin
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_buffer_position = 0;
|
||||||
|
_buffer_length = 0;
|
||||||
|
|
||||||
_connected = true;
|
_connected = true;
|
||||||
_length = _http->getSize() + offset;
|
_length = _http->getSize() + offset;
|
||||||
if (_http->hasHeader("Content-Type")) {
|
if (_http->hasHeader("Content-Type")) {
|
||||||
@ -208,3 +211,12 @@ String HTTPClientWrapper::readUntil(String sep) {
|
|||||||
String HTTPClientWrapper::readLine() {
|
String HTTPClientWrapper::readLine() {
|
||||||
return readUntil("\n\r");
|
return readUntil("\n\r");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int HTTPClientWrapper::peek(int offset) {
|
||||||
|
if (_buffer_position >= _buffer_length) _fill_buffer();
|
||||||
|
|
||||||
|
if (_buffer_position + offset < 0 || _buffer_position + offset >= _buffer_length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return _buffer[_buffer_position + offset];
|
||||||
|
}
|
@ -12,7 +12,12 @@ HTTPServer::HTTPServer(Player* p, Controller* c) {
|
|||||||
ws->onEvent([&](AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){this->_onEvent(server, client, type, arg, data, len);});
|
ws->onEvent([&](AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){this->_onEvent(server, client, type, arg, data, len);});
|
||||||
|
|
||||||
_server->on("/", HTTP_GET, [&](AsyncWebServerRequest* req) {
|
_server->on("/", HTTP_GET, [&](AsyncWebServerRequest* req) {
|
||||||
req->send(200, "text/html", (const char*)file_index_html_start);
|
req->send_P(200, "text/html", file_index_html_start, file_index_html_size);
|
||||||
|
});
|
||||||
|
_server->on("/timezone.json", HTTP_GET, [&](AsyncWebServerRequest* req) {
|
||||||
|
AsyncWebServerResponse* res = req->beginResponse_P(200, "application/json", file_timezones_json_start, file_timezones_json_size);
|
||||||
|
res->addHeader("Content-Encoding", "gzip");
|
||||||
|
req->send(res);
|
||||||
});
|
});
|
||||||
_server->on("/upload", HTTP_POST, [](AsyncWebServerRequest* req) {
|
_server->on("/upload", HTTP_POST, [](AsyncWebServerRequest* req) {
|
||||||
req->send(200);
|
req->send(200);
|
||||||
|
61
src/main.cpp
61
src/main.cpp
@ -2,7 +2,9 @@
|
|||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
#include <WiFiMulti.h>
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
|
#include <Preferences.h>
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
@ -19,34 +21,26 @@ HTTPServer* http_server;
|
|||||||
|
|
||||||
uint8_t SPIMaster::state = 0;
|
uint8_t SPIMaster::state = 0;
|
||||||
|
|
||||||
bool connect_to_wifi(String ssid, String pass) {
|
bool debug_enabled = true;
|
||||||
TRACE("Connecting to wifi \"%s\"...\n", ssid.c_str());
|
bool trace_enabled = false;
|
||||||
WiFi.mode(WIFI_AP_STA);
|
Preferences prefs;
|
||||||
WiFi.begin(ssid.c_str(), pass.c_str());
|
|
||||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
|
||||||
DEBUG("Could not connect to wifi \"%s\".\n", ssid.c_str());
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
INFO("Connected to \"%s\". IP address: %s\n", ssid.c_str(), WiFi.localIP().toString().c_str());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void wifi_connect() {
|
void wifi_connect() {
|
||||||
bool connected = false;
|
|
||||||
INFO("Connecting to WiFi...\n");
|
INFO("Connecting to WiFi...\n");
|
||||||
|
WiFiMulti wifi;
|
||||||
SPIMaster::select_sd();
|
SPIMaster::select_sd();
|
||||||
if (SD.exists("/_wifis.txt")) {
|
if (SD.exists("/_wifis.txt")) {
|
||||||
DEBUG("Reading /_wifis.txt\n");
|
DEBUG("Reading /_wifis.txt\n");
|
||||||
File f = SD.open("/_wifis.txt", "r");
|
File f = SD.open("/_wifis.txt", "r");
|
||||||
while (String line = f.readStringUntil('\n')) {
|
while (String line = f.readStringUntil('\n')) {
|
||||||
if (line.length()==0 || line.startsWith("#") || line.indexOf('=')==-1) {
|
if (line.length()==0) {
|
||||||
|
break;
|
||||||
|
} else if (line.startsWith("#") || line.indexOf('=')==-1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String ssid = line.substring(0, line.indexOf('='));
|
String ssid = line.substring(0, line.indexOf('='));
|
||||||
String pass = line.substring(line.indexOf('=')+1);
|
String pass = line.substring(line.indexOf('=')+1);
|
||||||
connected = connect_to_wifi(ssid, pass);
|
wifi.addAP(ssid.c_str(), pass.c_str());
|
||||||
if (connected) break;
|
|
||||||
}
|
}
|
||||||
f.close();
|
f.close();
|
||||||
} else {
|
} else {
|
||||||
@ -56,21 +50,20 @@ void wifi_connect() {
|
|||||||
}
|
}
|
||||||
SPIMaster::select_sd(false);
|
SPIMaster::select_sd(false);
|
||||||
|
|
||||||
if (!connected) {
|
#if defined(WIFI_SSID) and defined(WIFI_PASS)
|
||||||
#if defined(WIFI_SSID) and defined(WIFI_PASS)
|
wifi.addAP(WIFI_SSID, WIFI_PASS);
|
||||||
DEBUG("Trying hardcoded WiFi data...\n");
|
#endif
|
||||||
connected = connect_to_wifi(WIFI_SSID, WIFI_PASS);
|
|
||||||
#else
|
if (wifi.run() == WL_CONNECTED) {
|
||||||
DEBUG("No hardcoded WiFi data set.\n");
|
DEBUG("Connected to WiFi \"%s\".\n", WiFi.SSID().c_str());
|
||||||
#endif
|
} else {
|
||||||
}
|
DEBUG("No WiFi connection!\n");
|
||||||
if (!connected) {
|
|
||||||
INFO("No WiFi connection!\n");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
delay(500);
|
// Small delay to give the Serial console a bit of time to connect.
|
||||||
|
delay(1000);
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
Serial.println("Starting...");
|
Serial.println("Starting...");
|
||||||
Serial.println("Started.");
|
Serial.println("Started.");
|
||||||
@ -81,6 +74,14 @@ void setup() {
|
|||||||
INFO("ESMP3, version unknown (OTA_VERSION %d)\n", OTA_VERSION);
|
INFO("ESMP3, version unknown (OTA_VERSION %d)\n", OTA_VERSION);
|
||||||
#endif
|
#endif
|
||||||
INFO("Initializing...\n");
|
INFO("Initializing...\n");
|
||||||
|
prefs.begin("esmp3");
|
||||||
|
debug_enabled = prefs.getBool("debug_enabled", true);
|
||||||
|
trace_enabled = prefs.getBool("trace_enabled", false);
|
||||||
|
|
||||||
|
PIN_SPEAKER_L_SETUP();
|
||||||
|
PIN_SPEAKER_R_SETUP();
|
||||||
|
PIN_SPEAKER_L(LOW);
|
||||||
|
PIN_SPEAKER_R(LOW);
|
||||||
|
|
||||||
DEBUG("Setting up SPI...\n");
|
DEBUG("Setting up SPI...\n");
|
||||||
SPI.begin();
|
SPI.begin();
|
||||||
@ -126,6 +127,12 @@ void setup() {
|
|||||||
INFO("Could not fetch current time via NTP.\n");
|
INFO("Could not fetch current time via NTP.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef VERSION
|
||||||
|
INFO("ESMP3 version %s (OTA_VERSION %d)\n", VERSION, OTA_VERSION);
|
||||||
|
#else
|
||||||
|
INFO("ESMP3, version unknown (OTA_VERSION %d)\n", OTA_VERSION);
|
||||||
|
#endif
|
||||||
|
|
||||||
INFO("Initialization completed.\n");
|
INFO("Initialization completed.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,8 +10,6 @@ Player::Player(SPIMaster* s) {
|
|||||||
_spi = s;
|
_spi = s;
|
||||||
PIN_VS1053_XRESET_SETUP();
|
PIN_VS1053_XRESET_SETUP();
|
||||||
PIN_VS1053_XRESET(HIGH);
|
PIN_VS1053_XRESET(HIGH);
|
||||||
PIN_SPEAKER_L_SETUP();
|
|
||||||
PIN_SPEAKER_R_SETUP();
|
|
||||||
_speaker_off();
|
_speaker_off();
|
||||||
_spi->disable();
|
_spi->disable();
|
||||||
PIN_VS1053_DREQ_SETUP();
|
PIN_VS1053_DREQ_SETUP();
|
||||||
@ -464,14 +462,14 @@ void Player::set_volume(uint8_t vol, bool save) {
|
|||||||
|
|
||||||
void Player::vol_up() {
|
void Player::vol_up() {
|
||||||
if (!is_playing()) return;
|
if (!is_playing()) return;
|
||||||
uint8_t vol = _volume + VOLUME_STEP;
|
uint16_t vol = _volume + VOLUME_STEP;
|
||||||
if (vol > VOLUME_MAX) vol=VOLUME_MAX;
|
if (vol > VOLUME_MAX) vol=VOLUME_MAX;
|
||||||
set_volume(vol);
|
set_volume(vol);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::vol_down() {
|
void Player::vol_down() {
|
||||||
if (!is_playing()) return;
|
if (!is_playing()) return;
|
||||||
uint8_t vol = _volume - VOLUME_STEP;
|
int16_t vol = _volume - VOLUME_STEP;
|
||||||
if (vol < VOLUME_MIN) vol=VOLUME_MIN;
|
if (vol < VOLUME_MIN) vol=VOLUME_MIN;
|
||||||
set_volume(vol);
|
set_volume(vol);
|
||||||
}
|
}
|
||||||
@ -534,6 +532,7 @@ bool Player::play() {
|
|||||||
if (_state != idle) return false;
|
if (_state != idle) return false;
|
||||||
if (_current_playlist == NULL) return false;
|
if (_current_playlist == NULL) return false;
|
||||||
if (_current_playlist->get_file_count()==0) return false;
|
if (_current_playlist->get_file_count()==0) return false;
|
||||||
|
_speaker_on();
|
||||||
_current_playlist->start();
|
_current_playlist->start();
|
||||||
String file;
|
String file;
|
||||||
if (!_current_playlist->get_current_file(&file)) {
|
if (!_current_playlist->get_current_file(&file)) {
|
||||||
|
@ -77,12 +77,11 @@ void Playlist::_examine_http_url(String url) {
|
|||||||
String ct = http->getContentType();
|
String ct = http->getContentType();
|
||||||
DEBUG("Content-Type is %s.\n", ct.c_str());
|
DEBUG("Content-Type is %s.\n", ct.c_str());
|
||||||
if (ct.startsWith("audio/x-mpegurl")) {
|
if (ct.startsWith("audio/x-mpegurl")) {
|
||||||
|
|
||||||
_parse_m3u(http);
|
_parse_m3u(http);
|
||||||
} else if (ct.startsWith("audio/")) {
|
} else if (ct.startsWith("audio/")) {
|
||||||
persistence = PERSIST_NONE;
|
persistence = PERSIST_NONE;
|
||||||
_files.push_back({.filename=url, .title=url, .id="none"});
|
_files.push_back({.filename=url, .title=url, .id="none"});
|
||||||
} else if (ct.startsWith("application/rss+xml")) {
|
} else if (ct.startsWith("application/rss+xml") || ct.startsWith("application/xml")) {
|
||||||
persistence = PERSIST_PERMANENTLY;
|
persistence = PERSIST_PERMANENTLY;
|
||||||
_parse_rss(http);
|
_parse_rss(http);
|
||||||
} else if (ct.startsWith("application/pls+xml")) {
|
} else if (ct.startsWith("application/pls+xml")) {
|
||||||
@ -108,36 +107,44 @@ void xmlcb(uint8_t status, char* tagName, uint16_t tagLen, char* data, uint16_t
|
|||||||
String tag(tagName);
|
String tag(tagName);
|
||||||
if (status & STATUS_START_TAG) xml_last_tag = tag;
|
if (status & STATUS_START_TAG) xml_last_tag = tag;
|
||||||
|
|
||||||
|
if (trace_enabled) {
|
||||||
|
if (status & STATUS_START_TAG) {
|
||||||
|
TRACE("Start of tag: %s\n", tagName);
|
||||||
|
} else if (status & STATUS_END_TAG) {
|
||||||
|
TRACE("End of tag: %s\n", tagName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tag.equals("/rss/channel/title") && (status & STATUS_TAG_TEXT)) {
|
if (tag.equals("/rss/channel/title") && (status & STATUS_TAG_TEXT)) {
|
||||||
xml_album_title = data;
|
xml_album_title = data;
|
||||||
} else if (tag.endsWith("/item") && (status & STATUS_START_TAG)) {
|
} else if (tag.endsWith("/title") && (status & STATUS_TAG_TEXT)) {
|
||||||
xml_title = "";
|
|
||||||
xml_url = "";
|
|
||||||
xml_guid = "";
|
|
||||||
} else if (tag.endsWith("/item/title") && (status & STATUS_TAG_TEXT)) {
|
|
||||||
xml_title = String(data);
|
xml_title = String(data);
|
||||||
} else if (tag.endsWith("/item/guid") && (status & STATUS_TAG_TEXT)) {
|
} else if (tag.endsWith("/guid") && (status & STATUS_TAG_TEXT)) {
|
||||||
xml_guid = data;
|
xml_guid = data;
|
||||||
//} else if (xml_last_tag.endsWith("/item/enclosure") && (status & STATUS_ATTR_TEXT)) {
|
//} else if (xml_last_tag.endsWith("/item/enclosure") && (status & STATUS_ATTR_TEXT)) {
|
||||||
// DEBUG("tag: %s, data: %s\n", tag.c_str(), data);
|
// DEBUG("tag: %s, data: %s\n", tag.c_str(), data);
|
||||||
} else if (xml_last_tag.endsWith("/item/enclosure") && tag.equals("type") && (status & STATUS_ATTR_TEXT) && String(data).indexOf("audio/")>=0) {
|
} else if (xml_last_tag.endsWith("/enclosure") && tag.equals("type") && (status & STATUS_ATTR_TEXT) && String(data).indexOf("audio/")>=0) {
|
||||||
DEBUG("enclosure is audio\n");
|
DEBUG("enclosure is audio\n");
|
||||||
xml_enclosure_is_audio = true;
|
xml_enclosure_is_audio = true;
|
||||||
} else if (xml_last_tag.endsWith("/item/enclosure") && tag.equals("url") && (status & STATUS_ATTR_TEXT)) {
|
} else if (xml_last_tag.endsWith("/enclosure") && tag.equals("url") && (status & STATUS_ATTR_TEXT)) {
|
||||||
DEBUG("found url\n");
|
DEBUG("found url\n");
|
||||||
xml_enclosure_url = String(data);
|
xml_enclosure_url = String(data);
|
||||||
} else if (tag.endsWith("/item/enclosure") && (status & STATUS_END_TAG)) {
|
} else if (tag.endsWith("/enclosure") && (status & STATUS_END_TAG)) {
|
||||||
DEBUG("end of enclosure. xml_enclosure_is_audio: %d, xml_enclosure_url: %s\n", xml_enclosure_is_audio, xml_enclosure_url.c_str());
|
DEBUG("end of enclosure. xml_enclosure_is_audio: %d, xml_enclosure_url: %s\n", xml_enclosure_is_audio, xml_enclosure_url.c_str());
|
||||||
if (xml_enclosure_is_audio && xml_enclosure_url.length()>0) {
|
if (xml_enclosure_is_audio && xml_enclosure_url.length()>0) {
|
||||||
xml_url = xml_enclosure_url;
|
xml_url = xml_enclosure_url;
|
||||||
}
|
}
|
||||||
xml_enclosure_is_audio = false;
|
xml_enclosure_is_audio = false;
|
||||||
xml_enclosure_url = "";
|
xml_enclosure_url = "";
|
||||||
} else if (tag.endsWith("/item") && (status & STATUS_END_TAG)) {
|
} else if (tag.endsWith("/item") && (status & STATUS_END_TAG || status & STATUS_START_TAG)) {
|
||||||
if (xml_title.length()>0 && xml_url.length()>0) {
|
if (xml_title.length()>0 && xml_url.length()>0) {
|
||||||
|
if (xml_files_ptr->size() > 20) return;
|
||||||
DEBUG("Adding playlist entry: '%s' => '%s'\n", xml_title.c_str(), xml_url.c_str());
|
DEBUG("Adding playlist entry: '%s' => '%s'\n", xml_title.c_str(), xml_url.c_str());
|
||||||
xml_files_ptr->insert(xml_files_ptr->begin(), {.filename=xml_url, .title=xml_title, .id=xml_guid});
|
xml_files_ptr->insert(xml_files_ptr->begin(), {.filename=xml_url, .title=xml_title, .id=xml_guid});
|
||||||
}
|
}
|
||||||
|
xml_title = "";
|
||||||
|
xml_url = "";
|
||||||
|
xml_guid = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +165,7 @@ void Playlist::_parse_rss(HTTPClientWrapper* http) {
|
|||||||
while ((i = http->read()) >= 0) {
|
while ((i = http->read()) >= 0) {
|
||||||
xml.processChar(i);
|
xml.processChar(i);
|
||||||
}
|
}
|
||||||
|
_current_track = _files.size()-1;
|
||||||
xml_files_ptr = NULL;
|
xml_files_ptr = NULL;
|
||||||
if (xml_album_title.length()>0) {
|
if (xml_album_title.length()>0) {
|
||||||
_title = xml_album_title;
|
_title = xml_album_title;
|
||||||
@ -176,7 +184,7 @@ void Playlist::_parse_m3u(HTTPClientWrapper* http) {
|
|||||||
do {
|
do {
|
||||||
i = http->read();
|
i = http->read();
|
||||||
char c = i;
|
char c = i;
|
||||||
if (i>=-1 && c!='\r' && c!='\n') {
|
if (i>=0 && c!='\r' && c!='\n') {
|
||||||
line += c;
|
line += c;
|
||||||
} else {
|
} else {
|
||||||
if (line.equals("#EXTM3U")) {
|
if (line.equals("#EXTM3U")) {
|
||||||
@ -317,12 +325,19 @@ void Playlist::shuffle(uint8_t random_offset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::advent_shuffle(uint8_t day) {
|
void Playlist::advent_shuffle(uint8_t day) {
|
||||||
if (day > 24) day = 24;
|
TRACE("advent_shuffle running...\n");
|
||||||
|
|
||||||
|
// Not enough songs till the current day? Play all songs in the default order.
|
||||||
|
if (day > _files.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are in the "different playlist every day" mode. So we don't persist it in order to not miss changes.
|
||||||
|
persistence = PERSIST_NONE;
|
||||||
|
|
||||||
if (day > _files.size()) return;
|
|
||||||
|
|
||||||
_files.insert(_files.begin(), _files[day - 1]);
|
_files.insert(_files.begin(), _files[day - 1]);
|
||||||
_files.erase(_files.begin() + day - 1, _files.end());
|
_files.erase(_files.begin() + day, _files.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::reset() {
|
void Playlist::reset() {
|
||||||
@ -375,6 +390,7 @@ void Playlist::json(JsonObject json) {
|
|||||||
JsonObject o = files.createNestedObject();
|
JsonObject o = files.createNestedObject();
|
||||||
o["filename"] = entry.filename;
|
o["filename"] = entry.filename;
|
||||||
o["title"] = entry.title;
|
o["title"] = entry.title;
|
||||||
|
o["id"] = entry.id;
|
||||||
}
|
}
|
||||||
json["current_track"] = _current_track;
|
json["current_track"] = _current_track;
|
||||||
json["has_track_next"] = has_track_next();
|
json["has_track_next"] = has_track_next();
|
||||||
|
@ -109,7 +109,6 @@ Playlist* PlaylistManager::get_playlist_for_folder(String folder) {
|
|||||||
p = new Playlist(folder);
|
p = new Playlist(folder);
|
||||||
_playlists[folder] = p;
|
_playlists[folder] = p;
|
||||||
if (p->persistence == PERSIST_PERMANENTLY) {
|
if (p->persistence == PERSIST_PERMANENTLY) {
|
||||||
// TODO Load persistence from file
|
|
||||||
String search = folder;
|
String search = folder;
|
||||||
search += "=";
|
search += "=";
|
||||||
SPIMaster::select_sd();
|
SPIMaster::select_sd();
|
||||||
@ -197,38 +196,44 @@ String PlaylistManager::create_mapping_txt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistManager::persist(Playlist* p) {
|
void PlaylistManager::persist(Playlist* p) {
|
||||||
if (p->persistence != PERSIST_PERMANENTLY) return;
|
if (p->persistence == PERSIST_NONE) {
|
||||||
|
_playlists.erase(p->path());
|
||||||
|
return;
|
||||||
|
} else if (p->persistence == PERSIST_PERMANENTLY) {
|
||||||
|
|
||||||
String search = p->path();
|
String search = p->path();
|
||||||
search += '=';
|
search += '=';
|
||||||
|
|
||||||
bool old_file_existed = false;
|
bool old_file_existed = false;
|
||||||
|
|
||||||
SPIMaster::select_sd();
|
SPIMaster::select_sd();
|
||||||
if (SD.exists("_positions.txt")) {
|
if (SD.exists("_positions.txt")) {
|
||||||
SD.rename("/_positions.txt", "/_positions.temp.txt");
|
SD.rename("/_positions.txt", "/_positions.temp.txt");
|
||||||
old_file_existed = true;
|
old_file_existed = true;
|
||||||
}
|
}
|
||||||
File dst = SD.open("/_positions.txt", "w");
|
File dst = SD.open("/_positions.txt", "w");
|
||||||
|
|
||||||
if (old_file_existed) {
|
if (old_file_existed) {
|
||||||
File src = SD.open("/_positions.temp.txt", "r");
|
File src = SD.open("/_positions.temp.txt", "r");
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
String line = src.readStringUntil('\n');
|
String line = src.readStringUntil('\n');
|
||||||
line.trim();
|
line.trim();
|
||||||
if (line.startsWith(search)) continue;
|
if (line.startsWith(search)) continue;
|
||||||
dst.println(line);
|
dst.println(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
src.close();
|
||||||
|
SD.remove("/_positions.temp.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
src.close();
|
dst.print(search);
|
||||||
SD.remove("/_positions.temp.txt");
|
dst.print(p->get_current_track_id());
|
||||||
}
|
dst.print(',');
|
||||||
|
dst.println(p->get_position());
|
||||||
|
dst.close();
|
||||||
|
SPIMaster::select_sd(false);
|
||||||
|
|
||||||
dst.print(search);
|
_playlists.erase(p->path());
|
||||||
dst.print(p->get_current_track_id());
|
}
|
||||||
dst.print(',');
|
|
||||||
dst.println(p->get_position());
|
|
||||||
dst.close();
|
|
||||||
SPIMaster::select_sd(false);
|
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,24 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
||||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
||||||
<script src="https://kit.fontawesome.com/272149490a.js" crossorigin="anonymous"></script>
|
<script src="https://kit.fontawesome.com/272149490a.js" crossorigin="anonymous"></script>
|
||||||
|
<style>
|
||||||
|
.overlay {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.85);
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
margin: auto 0px;
|
||||||
|
vertical-align: middle;
|
||||||
|
color: white;
|
||||||
|
font-size: 60px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div id="overlay" class="overlay">Not connected...</div>
|
||||||
<div class="container bg-dark text-light">
|
<div class="container bg-dark text-light">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-1">
|
<div class="col-sm-1">
|
||||||
@ -104,7 +119,7 @@
|
|||||||
<input type="text" class="form-control" id="input_url" />
|
<input type="text" class="form-control" id="input_url" />
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button class="btn btn-primary" id="button_url_open">Go</button>
|
<button class="btn btn-primary" id="button_url_open">Go</button>
|
||||||
<button class="btn btn-danger" id="button_url_add_mapping" style="display: none;"><i class="fa fa-arrows-alt-h"></i></button>
|
<button class="btn btn-warning" id="button_url_add_mapping" style="display: none;"><i class="fa fa-arrows-alt-h"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -188,7 +203,7 @@ update_playlist = function(data) {
|
|||||||
tr = $('<tr>').data('track', i);
|
tr = $('<tr>').data('track', i);
|
||||||
tr.append($('<td>').html(i + 1));
|
tr.append($('<td>').html(i + 1));
|
||||||
tr.append($('<td>').html(data.current_track==i ? '<i class="fa fa-play"></i>' : ''));
|
tr.append($('<td>').html(data.current_track==i ? '<i class="fa fa-play"></i>' : ''));
|
||||||
tr.append($('<td>').html(data.files[i].substr(data.files[i].title)));
|
tr.append($('<td>').html(data.files[i].title));
|
||||||
$('#track_list').append(tr);
|
$('#track_list').append(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,10 +288,30 @@ process_ws_message = function(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var play_on_click = true;
|
var play_on_click = true;
|
||||||
|
interval = null;
|
||||||
|
ws = null;
|
||||||
|
|
||||||
|
var start_reconnect_timer = function() {
|
||||||
|
console.log("start_reconnect_timer() running...");
|
||||||
|
$('#overlay').show();
|
||||||
|
interval = setInterval(connect_to_ws, 2500);
|
||||||
|
}
|
||||||
|
|
||||||
|
var connect_to_ws = function() {
|
||||||
|
if (!ws || ws.readyState >= ws.CLOSING) {
|
||||||
|
ws = new WebSocket("ws://" + location.host + "/ws");
|
||||||
|
ws.onopen = function() {
|
||||||
|
console.log("on_open() running...");
|
||||||
|
clearInterval(interval);
|
||||||
|
$('#overlay').hide();
|
||||||
|
};
|
||||||
|
ws.onmessage = process_ws_message;
|
||||||
|
ws.onclose = start_reconnect_timer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
ws = new WebSocket("ws://" + location.host + "/ws");
|
start_reconnect_timer();
|
||||||
ws.onmessage = process_ws_message;
|
|
||||||
|
|
||||||
$('#volume_slider').change(function(e) { ws.send("volume=" + e.target.value); });
|
$('#volume_slider').change(function(e) { ws.send("volume=" + e.target.value); });
|
||||||
$('#button_play').click(function(e) { ws.send("play"); });
|
$('#button_play').click(function(e) { ws.send("play"); });
|
462
src/webinterface/timezones.json
Normal file
462
src/webinterface/timezones.json
Normal file
@ -0,0 +1,462 @@
|
|||||||
|
{"timezones": {
|
||||||
|
"Africa/Abidjan":"GMT0",
|
||||||
|
"Africa/Accra":"GMT0",
|
||||||
|
"Africa/Addis_Ababa":"EAT-3",
|
||||||
|
"Africa/Algiers":"CET-1",
|
||||||
|
"Africa/Asmara":"EAT-3",
|
||||||
|
"Africa/Bamako":"GMT0",
|
||||||
|
"Africa/Bangui":"WAT-1",
|
||||||
|
"Africa/Banjul":"GMT0",
|
||||||
|
"Africa/Bissau":"GMT0",
|
||||||
|
"Africa/Blantyre":"CAT-2",
|
||||||
|
"Africa/Brazzaville":"WAT-1",
|
||||||
|
"Africa/Bujumbura":"CAT-2",
|
||||||
|
"Africa/Cairo":"EET-2",
|
||||||
|
"Africa/Casablanca":"<+01>-1",
|
||||||
|
"Africa/Ceuta":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Africa/Conakry":"GMT0",
|
||||||
|
"Africa/Dakar":"GMT0",
|
||||||
|
"Africa/Dar_es_Salaam":"EAT-3",
|
||||||
|
"Africa/Djibouti":"EAT-3",
|
||||||
|
"Africa/Douala":"WAT-1",
|
||||||
|
"Africa/El_Aaiun":"<+01>-1",
|
||||||
|
"Africa/Freetown":"GMT0",
|
||||||
|
"Africa/Gaborone":"CAT-2",
|
||||||
|
"Africa/Harare":"CAT-2",
|
||||||
|
"Africa/Johannesburg":"SAST-2",
|
||||||
|
"Africa/Juba":"EAT-3",
|
||||||
|
"Africa/Kampala":"EAT-3",
|
||||||
|
"Africa/Khartoum":"CAT-2",
|
||||||
|
"Africa/Kigali":"CAT-2",
|
||||||
|
"Africa/Kinshasa":"WAT-1",
|
||||||
|
"Africa/Lagos":"WAT-1",
|
||||||
|
"Africa/Libreville":"WAT-1",
|
||||||
|
"Africa/Lome":"GMT0",
|
||||||
|
"Africa/Luanda":"WAT-1",
|
||||||
|
"Africa/Lubumbashi":"CAT-2",
|
||||||
|
"Africa/Lusaka":"CAT-2",
|
||||||
|
"Africa/Malabo":"WAT-1",
|
||||||
|
"Africa/Maputo":"CAT-2",
|
||||||
|
"Africa/Maseru":"SAST-2",
|
||||||
|
"Africa/Mbabane":"SAST-2",
|
||||||
|
"Africa/Mogadishu":"EAT-3",
|
||||||
|
"Africa/Monrovia":"GMT0",
|
||||||
|
"Africa/Nairobi":"EAT-3",
|
||||||
|
"Africa/Ndjamena":"WAT-1",
|
||||||
|
"Africa/Niamey":"WAT-1",
|
||||||
|
"Africa/Nouakchott":"GMT0",
|
||||||
|
"Africa/Ouagadougou":"GMT0",
|
||||||
|
"Africa/Porto-Novo":"WAT-1",
|
||||||
|
"Africa/Sao_Tome":"GMT0",
|
||||||
|
"Africa/Tripoli":"EET-2",
|
||||||
|
"Africa/Tunis":"CET-1",
|
||||||
|
"Africa/Windhoek":"CAT-2",
|
||||||
|
"America/Adak":"HST10HDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Anchorage":"AKST9AKDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Anguilla":"AST4",
|
||||||
|
"America/Antigua":"AST4",
|
||||||
|
"America/Araguaina":"<-03>3",
|
||||||
|
"America/Argentina/Buenos_Aires":"<-03>3",
|
||||||
|
"America/Argentina/Catamarca":"<-03>3",
|
||||||
|
"America/Argentina/Cordoba":"<-03>3",
|
||||||
|
"America/Argentina/Jujuy":"<-03>3",
|
||||||
|
"America/Argentina/La_Rioja":"<-03>3",
|
||||||
|
"America/Argentina/Mendoza":"<-03>3",
|
||||||
|
"America/Argentina/Rio_Gallegos":"<-03>3",
|
||||||
|
"America/Argentina/Salta":"<-03>3",
|
||||||
|
"America/Argentina/San_Juan":"<-03>3",
|
||||||
|
"America/Argentina/San_Luis":"<-03>3",
|
||||||
|
"America/Argentina/Tucuman":"<-03>3",
|
||||||
|
"America/Argentina/Ushuaia":"<-03>3",
|
||||||
|
"America/Aruba":"AST4",
|
||||||
|
"America/Asuncion":"<-04>4<-03>,M10.1.0/0,M3.4.0/0",
|
||||||
|
"America/Atikokan":"EST5",
|
||||||
|
"America/Bahia":"<-03>3",
|
||||||
|
"America/Bahia_Banderas":"CST6CDT,M4.1.0,M10.5.0",
|
||||||
|
"America/Barbados":"AST4",
|
||||||
|
"America/Belem":"<-03>3",
|
||||||
|
"America/Belize":"CST6",
|
||||||
|
"America/Blanc-Sablon":"AST4",
|
||||||
|
"America/Boa_Vista":"<-04>4",
|
||||||
|
"America/Bogota":"<-05>5",
|
||||||
|
"America/Boise":"MST7MDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Cambridge_Bay":"MST7MDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Campo_Grande":"<-04>4",
|
||||||
|
"America/Cancun":"EST5",
|
||||||
|
"America/Caracas":"<-04>4",
|
||||||
|
"America/Cayenne":"<-03>3",
|
||||||
|
"America/Cayman":"EST5",
|
||||||
|
"America/Chicago":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Chihuahua":"MST7MDT,M4.1.0,M10.5.0",
|
||||||
|
"America/Costa_Rica":"CST6",
|
||||||
|
"America/Creston":"MST7",
|
||||||
|
"America/Cuiaba":"<-04>4",
|
||||||
|
"America/Curacao":"AST4",
|
||||||
|
"America/Danmarkshavn":"GMT0",
|
||||||
|
"America/Dawson":"PST8PDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Dawson_Creek":"MST7",
|
||||||
|
"America/Denver":"MST7MDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Detroit":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Dominica":"AST4",
|
||||||
|
"America/Edmonton":"MST7MDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Eirunepe":"<-05>5",
|
||||||
|
"America/El_Salvador":"CST6",
|
||||||
|
"America/Fortaleza":"<-03>3",
|
||||||
|
"America/Fort_Nelson":"MST7",
|
||||||
|
"America/Glace_Bay":"AST4ADT,M3.2.0,M11.1.0",
|
||||||
|
"America/Godthab":"<-03>3<-02>,M3.5.0/-2,M10.5.0/-1",
|
||||||
|
"America/Goose_Bay":"AST4ADT,M3.2.0,M11.1.0",
|
||||||
|
"America/Grand_Turk":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Grenada":"AST4",
|
||||||
|
"America/Guadeloupe":"AST4",
|
||||||
|
"America/Guatemala":"CST6",
|
||||||
|
"America/Guayaquil":"<-05>5",
|
||||||
|
"America/Guyana":"<-04>4",
|
||||||
|
"America/Halifax":"AST4ADT,M3.2.0,M11.1.0",
|
||||||
|
"America/Havana":"CST5CDT,M3.2.0/0,M11.1.0/1",
|
||||||
|
"America/Hermosillo":"MST7",
|
||||||
|
"America/Indiana/Indianapolis":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Indiana/Knox":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Indiana/Marengo":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Indiana/Petersburg":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Indiana/Tell_City":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Indiana/Vevay":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Indiana/Vincennes":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Indiana/Winamac":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Inuvik":"MST7MDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Iqaluit":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Jamaica":"EST5",
|
||||||
|
"America/Juneau":"AKST9AKDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Kentucky/Louisville":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Kentucky/Monticello":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Kralendijk":"AST4",
|
||||||
|
"America/La_Paz":"<-04>4",
|
||||||
|
"America/Lima":"<-05>5",
|
||||||
|
"America/Los_Angeles":"PST8PDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Lower_Princes":"AST4",
|
||||||
|
"America/Maceio":"<-03>3",
|
||||||
|
"America/Managua":"CST6",
|
||||||
|
"America/Manaus":"<-04>4",
|
||||||
|
"America/Marigot":"AST4",
|
||||||
|
"America/Martinique":"AST4",
|
||||||
|
"America/Matamoros":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Mazatlan":"MST7MDT,M4.1.0,M10.5.0",
|
||||||
|
"America/Menominee":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Merida":"CST6CDT,M4.1.0,M10.5.0",
|
||||||
|
"America/Metlakatla":"AKST9AKDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Mexico_City":"CST6CDT,M4.1.0,M10.5.0",
|
||||||
|
"America/Miquelon":"<-03>3<-02>,M3.2.0,M11.1.0",
|
||||||
|
"America/Moncton":"AST4ADT,M3.2.0,M11.1.0",
|
||||||
|
"America/Monterrey":"CST6CDT,M4.1.0,M10.5.0",
|
||||||
|
"America/Montevideo":"<-03>3",
|
||||||
|
"America/Montreal":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Montserrat":"AST4",
|
||||||
|
"America/Nassau":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/New_York":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Nipigon":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Nome":"AKST9AKDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Noronha":"<-02>2",
|
||||||
|
"America/North_Dakota/Beulah":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
|
"America/North_Dakota/Center":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
|
"America/North_Dakota/New_Salem":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Ojinaga":"MST7MDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Panama":"EST5",
|
||||||
|
"America/Pangnirtung":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Paramaribo":"<-03>3",
|
||||||
|
"America/Phoenix":"MST7",
|
||||||
|
"America/Port-au-Prince":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Port_of_Spain":"AST4",
|
||||||
|
"America/Porto_Velho":"<-04>4",
|
||||||
|
"America/Puerto_Rico":"AST4",
|
||||||
|
"America/Punta_Arenas":"<-03>3",
|
||||||
|
"America/Rainy_River":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Rankin_Inlet":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Recife":"<-03>3",
|
||||||
|
"America/Regina":"CST6",
|
||||||
|
"America/Resolute":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Rio_Branco":"<-05>5",
|
||||||
|
"America/Santarem":"<-03>3",
|
||||||
|
"America/Santiago":"<-04>4<-03>,M9.1.6/24,M4.1.6/24",
|
||||||
|
"America/Santo_Domingo":"AST4",
|
||||||
|
"America/Sao_Paulo":"<-03>3",
|
||||||
|
"America/Scoresbysund":"<-01>1<+00>,M3.5.0/0,M10.5.0/1",
|
||||||
|
"America/Sitka":"AKST9AKDT,M3.2.0,M11.1.0",
|
||||||
|
"America/St_Barthelemy":"AST4",
|
||||||
|
"America/St_Johns":"NST3:30NDT,M3.2.0,M11.1.0",
|
||||||
|
"America/St_Kitts":"AST4",
|
||||||
|
"America/St_Lucia":"AST4",
|
||||||
|
"America/St_Thomas":"AST4",
|
||||||
|
"America/St_Vincent":"AST4",
|
||||||
|
"America/Swift_Current":"CST6",
|
||||||
|
"America/Tegucigalpa":"CST6",
|
||||||
|
"America/Thule":"AST4ADT,M3.2.0,M11.1.0",
|
||||||
|
"America/Thunder_Bay":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Tijuana":"PST8PDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Toronto":"EST5EDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Tortola":"AST4",
|
||||||
|
"America/Vancouver":"PST8PDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Whitehorse":"PST8PDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Winnipeg":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Yakutat":"AKST9AKDT,M3.2.0,M11.1.0",
|
||||||
|
"America/Yellowknife":"MST7MDT,M3.2.0,M11.1.0",
|
||||||
|
"Antarctica/Casey":"<+08>-8",
|
||||||
|
"Antarctica/Davis":"<+07>-7",
|
||||||
|
"Antarctica/DumontDUrville":"<+10>-10",
|
||||||
|
"Antarctica/Macquarie":"<+11>-11",
|
||||||
|
"Antarctica/Mawson":"<+05>-5",
|
||||||
|
"Antarctica/McMurdo":"NZST-12NZDT,M9.5.0,M4.1.0/3",
|
||||||
|
"Antarctica/Palmer":"<-03>3",
|
||||||
|
"Antarctica/Rothera":"<-03>3",
|
||||||
|
"Antarctica/Syowa":"<+03>-3",
|
||||||
|
"Antarctica/Troll":"<+00>0<+02>-2,M3.5.0/1,M10.5.0/3",
|
||||||
|
"Antarctica/Vostok":"<+06>-6",
|
||||||
|
"Arctic/Longyearbyen":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Asia/Aden":"<+03>-3",
|
||||||
|
"Asia/Almaty":"<+06>-6",
|
||||||
|
"Asia/Amman":"EET-2EEST,M3.5.4/24,M10.5.5/1",
|
||||||
|
"Asia/Anadyr":"<+12>-12",
|
||||||
|
"Asia/Aqtau":"<+05>-5",
|
||||||
|
"Asia/Aqtobe":"<+05>-5",
|
||||||
|
"Asia/Ashgabat":"<+05>-5",
|
||||||
|
"Asia/Atyrau":"<+05>-5",
|
||||||
|
"Asia/Baghdad":"<+03>-3",
|
||||||
|
"Asia/Bahrain":"<+03>-3",
|
||||||
|
"Asia/Baku":"<+04>-4",
|
||||||
|
"Asia/Bangkok":"<+07>-7",
|
||||||
|
"Asia/Barnaul":"<+07>-7",
|
||||||
|
"Asia/Beirut":"EET-2EEST,M3.5.0/0,M10.5.0/0",
|
||||||
|
"Asia/Bishkek":"<+06>-6",
|
||||||
|
"Asia/Brunei":"<+08>-8",
|
||||||
|
"Asia/Chita":"<+09>-9",
|
||||||
|
"Asia/Choibalsan":"<+08>-8",
|
||||||
|
"Asia/Colombo":"<+0530>-5:30",
|
||||||
|
"Asia/Damascus":"EET-2EEST,M3.5.5/0,M10.5.5/0",
|
||||||
|
"Asia/Dhaka":"<+06>-6",
|
||||||
|
"Asia/Dili":"<+09>-9",
|
||||||
|
"Asia/Dubai":"<+04>-4",
|
||||||
|
"Asia/Dushanbe":"<+05>-5",
|
||||||
|
"Asia/Famagusta":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Asia/Gaza":"EET-2EEST,M3.5.5/0,M10.5.6/1",
|
||||||
|
"Asia/Hebron":"EET-2EEST,M3.5.5/0,M10.5.6/1",
|
||||||
|
"Asia/Ho_Chi_Minh":"<+07>-7",
|
||||||
|
"Asia/Hong_Kong":"HKT-8",
|
||||||
|
"Asia/Hovd":"<+07>-7",
|
||||||
|
"Asia/Irkutsk":"<+08>-8",
|
||||||
|
"Asia/Jakarta":"WIB-7",
|
||||||
|
"Asia/Jayapura":"WIT-9",
|
||||||
|
"Asia/Jerusalem":"IST-2IDT,M3.4.4/26,M10.5.0",
|
||||||
|
"Asia/Kabul":"<+0430>-4:30",
|
||||||
|
"Asia/Kamchatka":"<+12>-12",
|
||||||
|
"Asia/Karachi":"PKT-5",
|
||||||
|
"Asia/Kathmandu":"<+0545>-5:45",
|
||||||
|
"Asia/Khandyga":"<+09>-9",
|
||||||
|
"Asia/Kolkata":"IST-5:30",
|
||||||
|
"Asia/Krasnoyarsk":"<+07>-7",
|
||||||
|
"Asia/Kuala_Lumpur":"<+08>-8",
|
||||||
|
"Asia/Kuching":"<+08>-8",
|
||||||
|
"Asia/Kuwait":"<+03>-3",
|
||||||
|
"Asia/Macau":"CST-8",
|
||||||
|
"Asia/Magadan":"<+11>-11",
|
||||||
|
"Asia/Makassar":"WITA-8",
|
||||||
|
"Asia/Manila":"PST-8",
|
||||||
|
"Asia/Muscat":"<+04>-4",
|
||||||
|
"Asia/Nicosia":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Asia/Novokuznetsk":"<+07>-7",
|
||||||
|
"Asia/Novosibirsk":"<+07>-7",
|
||||||
|
"Asia/Omsk":"<+06>-6",
|
||||||
|
"Asia/Oral":"<+05>-5",
|
||||||
|
"Asia/Phnom_Penh":"<+07>-7",
|
||||||
|
"Asia/Pontianak":"WIB-7",
|
||||||
|
"Asia/Pyongyang":"KST-9",
|
||||||
|
"Asia/Qatar":"<+03>-3",
|
||||||
|
"Asia/Qyzylorda":"<+05>-5",
|
||||||
|
"Asia/Riyadh":"<+03>-3",
|
||||||
|
"Asia/Sakhalin":"<+11>-11",
|
||||||
|
"Asia/Samarkand":"<+05>-5",
|
||||||
|
"Asia/Seoul":"KST-9",
|
||||||
|
"Asia/Shanghai":"CST-8",
|
||||||
|
"Asia/Singapore":"<+08>-8",
|
||||||
|
"Asia/Srednekolymsk":"<+11>-11",
|
||||||
|
"Asia/Taipei":"CST-8",
|
||||||
|
"Asia/Tashkent":"<+05>-5",
|
||||||
|
"Asia/Tbilisi":"<+04>-4",
|
||||||
|
"Asia/Tehran":"<+0330>-3:30<+0430>,J79/24,J263/24",
|
||||||
|
"Asia/Thimphu":"<+06>-6",
|
||||||
|
"Asia/Tokyo":"JST-9",
|
||||||
|
"Asia/Tomsk":"<+07>-7",
|
||||||
|
"Asia/Ulaanbaatar":"<+08>-8",
|
||||||
|
"Asia/Urumqi":"<+06>-6",
|
||||||
|
"Asia/Ust-Nera":"<+10>-10",
|
||||||
|
"Asia/Vientiane":"<+07>-7",
|
||||||
|
"Asia/Vladivostok":"<+10>-10",
|
||||||
|
"Asia/Yakutsk":"<+09>-9",
|
||||||
|
"Asia/Yangon":"<+0630>-6:30",
|
||||||
|
"Asia/Yekaterinburg":"<+05>-5",
|
||||||
|
"Asia/Yerevan":"<+04>-4",
|
||||||
|
"Atlantic/Azores":"<-01>1<+00>,M3.5.0/0,M10.5.0/1",
|
||||||
|
"Atlantic/Bermuda":"AST4ADT,M3.2.0,M11.1.0",
|
||||||
|
"Atlantic/Canary":"WET0WEST,M3.5.0/1,M10.5.0",
|
||||||
|
"Atlantic/Cape_Verde":"<-01>1",
|
||||||
|
"Atlantic/Faroe":"WET0WEST,M3.5.0/1,M10.5.0",
|
||||||
|
"Atlantic/Madeira":"WET0WEST,M3.5.0/1,M10.5.0",
|
||||||
|
"Atlantic/Reykjavik":"GMT0",
|
||||||
|
"Atlantic/South_Georgia":"<-02>2",
|
||||||
|
"Atlantic/Stanley":"<-03>3",
|
||||||
|
"Atlantic/St_Helena":"GMT0",
|
||||||
|
"Australia/Adelaide":"ACST-9:30ACDT,M10.1.0,M4.1.0/3",
|
||||||
|
"Australia/Brisbane":"AEST-10",
|
||||||
|
"Australia/Broken_Hill":"ACST-9:30ACDT,M10.1.0,M4.1.0/3",
|
||||||
|
"Australia/Currie":"AEST-10AEDT,M10.1.0,M4.1.0/3",
|
||||||
|
"Australia/Darwin":"ACST-9:30",
|
||||||
|
"Australia/Eucla":"<+0845>-8:45",
|
||||||
|
"Australia/Hobart":"AEST-10AEDT,M10.1.0,M4.1.0/3",
|
||||||
|
"Australia/Lindeman":"AEST-10",
|
||||||
|
"Australia/Lord_Howe":"<+1030>-10:30<+11>-11,M10.1.0,M4.1.0",
|
||||||
|
"Australia/Melbourne":"AEST-10AEDT,M10.1.0,M4.1.0/3",
|
||||||
|
"Australia/Perth":"AWST-8",
|
||||||
|
"Australia/Sydney":"AEST-10AEDT,M10.1.0,M4.1.0/3",
|
||||||
|
"Europe/Amsterdam":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Andorra":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Astrakhan":"<+04>-4",
|
||||||
|
"Europe/Athens":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Europe/Belgrade":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Berlin":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Bratislava":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Brussels":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Bucharest":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Europe/Budapest":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Busingen":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Chisinau":"EET-2EEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Copenhagen":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Dublin":"IST-1GMT0,M10.5.0,M3.5.0/1",
|
||||||
|
"Europe/Gibraltar":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Guernsey":"GMT0BST,M3.5.0/1,M10.5.0",
|
||||||
|
"Europe/Helsinki":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Europe/Isle_of_Man":"GMT0BST,M3.5.0/1,M10.5.0",
|
||||||
|
"Europe/Istanbul":"<+03>-3",
|
||||||
|
"Europe/Jersey":"GMT0BST,M3.5.0/1,M10.5.0",
|
||||||
|
"Europe/Kaliningrad":"EET-2",
|
||||||
|
"Europe/Kiev":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Europe/Kirov":"<+03>-3",
|
||||||
|
"Europe/Lisbon":"WET0WEST,M3.5.0/1,M10.5.0",
|
||||||
|
"Europe/Ljubljana":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/London":"GMT0BST,M3.5.0/1,M10.5.0",
|
||||||
|
"Europe/Luxembourg":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Madrid":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Malta":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Mariehamn":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Europe/Minsk":"<+03>-3",
|
||||||
|
"Europe/Monaco":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Moscow":"MSK-3",
|
||||||
|
"Europe/Oslo":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Paris":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Podgorica":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Prague":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Riga":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Europe/Rome":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Samara":"<+04>-4",
|
||||||
|
"Europe/San_Marino":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Sarajevo":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Saratov":"<+04>-4",
|
||||||
|
"Europe/Simferopol":"MSK-3",
|
||||||
|
"Europe/Skopje":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Sofia":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Europe/Stockholm":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Tallinn":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Europe/Tirane":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Ulyanovsk":"<+04>-4",
|
||||||
|
"Europe/Uzhgorod":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Europe/Vaduz":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Vatican":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Vienna":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Vilnius":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Europe/Volgograd":"<+04>-4",
|
||||||
|
"Europe/Warsaw":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Zagreb":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Europe/Zaporozhye":"EET-2EEST,M3.5.0/3,M10.5.0/4",
|
||||||
|
"Europe/Zurich":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
|
"Indian/Antananarivo":"EAT-3",
|
||||||
|
"Indian/Chagos":"<+06>-6",
|
||||||
|
"Indian/Christmas":"<+07>-7",
|
||||||
|
"Indian/Cocos":"<+0630>-6:30",
|
||||||
|
"Indian/Comoro":"EAT-3",
|
||||||
|
"Indian/Kerguelen":"<+05>-5",
|
||||||
|
"Indian/Mahe":"<+04>-4",
|
||||||
|
"Indian/Maldives":"<+05>-5",
|
||||||
|
"Indian/Mauritius":"<+04>-4",
|
||||||
|
"Indian/Mayotte":"EAT-3",
|
||||||
|
"Indian/Reunion":"<+04>-4",
|
||||||
|
"Pacific/Apia":"<+13>-13<+14>,M9.5.0/3,M4.1.0/4",
|
||||||
|
"Pacific/Auckland":"NZST-12NZDT,M9.5.0,M4.1.0/3",
|
||||||
|
"Pacific/Bougainville":"<+11>-11",
|
||||||
|
"Pacific/Chatham":"<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45",
|
||||||
|
"Pacific/Chuuk":"<+10>-10",
|
||||||
|
"Pacific/Easter":"<-06>6<-05>,M9.1.6/22,M4.1.6/22",
|
||||||
|
"Pacific/Efate":"<+11>-11",
|
||||||
|
"Pacific/Enderbury":"<+13>-13",
|
||||||
|
"Pacific/Fakaofo":"<+13>-13",
|
||||||
|
"Pacific/Fiji":"<+12>-12<+13>,M11.2.0,M1.2.3/99",
|
||||||
|
"Pacific/Funafuti":"<+12>-12",
|
||||||
|
"Pacific/Galapagos":"<-06>6",
|
||||||
|
"Pacific/Gambier":"<-09>9",
|
||||||
|
"Pacific/Guadalcanal":"<+11>-11",
|
||||||
|
"Pacific/Guam":"ChST-10",
|
||||||
|
"Pacific/Honolulu":"HST10",
|
||||||
|
"Pacific/Kiritimati":"<+14>-14",
|
||||||
|
"Pacific/Kosrae":"<+11>-11",
|
||||||
|
"Pacific/Kwajalein":"<+12>-12",
|
||||||
|
"Pacific/Majuro":"<+12>-12",
|
||||||
|
"Pacific/Marquesas":"<-0930>9:30",
|
||||||
|
"Pacific/Midway":"SST11",
|
||||||
|
"Pacific/Nauru":"<+12>-12",
|
||||||
|
"Pacific/Niue":"<-11>11",
|
||||||
|
"Pacific/Norfolk":"<+11>-11<+12>,M10.1.0,M4.1.0/3",
|
||||||
|
"Pacific/Noumea":"<+11>-11",
|
||||||
|
"Pacific/Pago_Pago":"SST11",
|
||||||
|
"Pacific/Palau":"<+09>-9",
|
||||||
|
"Pacific/Pitcairn":"<-08>8",
|
||||||
|
"Pacific/Pohnpei":"<+11>-11",
|
||||||
|
"Pacific/Port_Moresby":"<+10>-10",
|
||||||
|
"Pacific/Rarotonga":"<-10>10",
|
||||||
|
"Pacific/Saipan":"ChST-10",
|
||||||
|
"Pacific/Tahiti":"<-10>10",
|
||||||
|
"Pacific/Tarawa":"<+12>-12",
|
||||||
|
"Pacific/Tongatapu":"<+13>-13",
|
||||||
|
"Pacific/Wake":"<+12>-12",
|
||||||
|
"Pacific/Wallis":"<+12>-12",
|
||||||
|
"Etc/GMT":"GMT0",
|
||||||
|
"Etc/GMT-0":"GMT0",
|
||||||
|
"Etc/GMT-1":"<+01>-1",
|
||||||
|
"Etc/GMT-2":"<+02>-2",
|
||||||
|
"Etc/GMT-3":"<+03>-3",
|
||||||
|
"Etc/GMT-4":"<+04>-4",
|
||||||
|
"Etc/GMT-5":"<+05>-5",
|
||||||
|
"Etc/GMT-6":"<+06>-6",
|
||||||
|
"Etc/GMT-7":"<+07>-7",
|
||||||
|
"Etc/GMT-8":"<+08>-8",
|
||||||
|
"Etc/GMT-9":"<+09>-9",
|
||||||
|
"Etc/GMT-10":"<+10>-10",
|
||||||
|
"Etc/GMT-11":"<+11>-11",
|
||||||
|
"Etc/GMT-12":"<+12>-12",
|
||||||
|
"Etc/GMT-13":"<+13>-13",
|
||||||
|
"Etc/GMT-14":"<+14>-14",
|
||||||
|
"Etc/GMT0":"GMT0",
|
||||||
|
"Etc/GMT+0":"GMT0",
|
||||||
|
"Etc/GMT+1":"<-01>1",
|
||||||
|
"Etc/GMT+2":"<-02>2",
|
||||||
|
"Etc/GMT+3":"<-03>3",
|
||||||
|
"Etc/GMT+4":"<-04>4",
|
||||||
|
"Etc/GMT+5":"<-05>5",
|
||||||
|
"Etc/GMT+6":"<-06>6",
|
||||||
|
"Etc/GMT+7":"<-07>7",
|
||||||
|
"Etc/GMT+8":"<-08>8",
|
||||||
|
"Etc/GMT+9":"<-09>9",
|
||||||
|
"Etc/GMT+10":"<-10>10",
|
||||||
|
"Etc/GMT+11":"<-11>11",
|
||||||
|
"Etc/GMT+12":"<-12>12",
|
||||||
|
"Etc/UCT":"UTC0",
|
||||||
|
"Etc/UTC":"UTC0",
|
||||||
|
"Etc/Greenwich":"GMT0",
|
||||||
|
"Etc/Universal":"UTC0",
|
||||||
|
"Etc/Zulu":"UTC0"
|
||||||
|
}}
|
14
tools/create_tz_json.sh
Normal file
14
tools/create_tz_json.sh
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
URL="https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv"
|
||||||
|
(
|
||||||
|
first=1
|
||||||
|
echo "{\"timezones\": {"
|
||||||
|
curl --silent "$URL" | while read line; do
|
||||||
|
[ $first -ne 1 ] && echo ","
|
||||||
|
first=0
|
||||||
|
echo -n "${line/\",\"/\":\"}"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
echo "}}"
|
||||||
|
) > src/webinterface/timezones.json
|
9
tools/post_build.py
Normal file
9
tools/post_build.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Import("env")
|
||||||
|
|
||||||
|
env.Execute("gzip -9 < src/webinterface/index.html > src/webinterface/index.html.gz")
|
||||||
|
env.Execute("gzip -9 < src/webinterface/timezones.json > src/webinterface/timezones.json.gz")
|
||||||
|
|
||||||
|
def build(source, target, env):
|
||||||
|
env.Execute("rm src/webinterface/index.html.gz src/webinterface/timezones.json.gz")
|
||||||
|
|
||||||
|
env.AddPostAction("buildprog", build)
|
Reference in New Issue
Block a user