mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2025-12-13 11:02:12 +01:00
v1.4.1
Add the ability to view receiver data.
This commit is contained in:
parent
7bd23e6bbe
commit
3f662b7bd0
11 changed files with 363 additions and 34 deletions
|
|
@ -3,7 +3,7 @@
|
||||||
#ifndef configsettings_h
|
#ifndef configsettings_h
|
||||||
#define configsettings_h
|
#define configsettings_h
|
||||||
|
|
||||||
#define FW_VERSION "v1.4.0"
|
#define FW_VERSION "v1.4.1"
|
||||||
enum DeviceStatus {
|
enum DeviceStatus {
|
||||||
DS_OK = 0,
|
DS_OK = 0,
|
||||||
DS_ERROR = 1,
|
DS_ERROR = 1,
|
||||||
|
|
|
||||||
77
Sockets.cpp
77
Sockets.cpp
|
|
@ -10,10 +10,40 @@
|
||||||
extern ConfigSettings settings;
|
extern ConfigSettings settings;
|
||||||
extern Network net;
|
extern Network net;
|
||||||
extern SomfyShadeController somfy;
|
extern SomfyShadeController somfy;
|
||||||
|
extern SocketEmitter sockEmit;
|
||||||
|
|
||||||
WebSocketsServer sockServer = WebSocketsServer(8080);
|
WebSocketsServer sockServer = WebSocketsServer(8080);
|
||||||
char g_buffer[1024];
|
|
||||||
|
|
||||||
|
bool room_t::isJoined(uint8_t num) {
|
||||||
|
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||||
|
if(this->clients[i] == num) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool room_t::join(uint8_t num) {
|
||||||
|
if(this->isJoined(num)) return true;
|
||||||
|
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||||
|
if(this->clients[i] == 255) {
|
||||||
|
this->clients[i] = num;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool room_t::leave(uint8_t num) {
|
||||||
|
if(!this->isJoined(num)) return false;
|
||||||
|
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||||
|
if(this->clients[i] == num) this->clients[i] = 255;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
uint8_t room_t::activeClients() {
|
||||||
|
uint8_t n = 0;
|
||||||
|
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||||
|
if(this->clients[i] != 255) n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
/*********************************************************************
|
/*********************************************************************
|
||||||
* ClientSocketEvent class members
|
* ClientSocketEvent class members
|
||||||
********************************************************************/
|
********************************************************************/
|
||||||
|
|
@ -46,6 +76,31 @@ bool SocketEmitter::sendToClient(uint8_t num, const char *evt, JsonObject &obj)
|
||||||
return this->sendToClient(num, evt, g_buffer);
|
return this->sendToClient(num, evt, g_buffer);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
ClientSocketEvent::ClientSocketEvent() {}
|
||||||
|
ClientSocketEvent::ClientSocketEvent(const char *evt) { snprintf(this->msg, sizeof(this->msg), "42[%s,]", evt); }
|
||||||
|
ClientSocketEvent::ClientSocketEvent(const char *evt, const char *payload) { snprintf(this->msg, sizeof(this->msg), "42[%s,%s]", evt, payload); }
|
||||||
|
void ClientSocketEvent::appendMessage(const char *text) {
|
||||||
|
uint16_t len = strlen(this->msg);
|
||||||
|
this->msg[len - 1] = '\0';
|
||||||
|
strcat(this->msg, text);
|
||||||
|
strcat(this->msg, "]");
|
||||||
|
}
|
||||||
|
uint8_t SocketEmitter::activeClients(uint8_t room) {
|
||||||
|
if(room < SOCK_MAX_ROOMS) return this->rooms[room].activeClients();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
bool SocketEmitter::sendToRoom(uint8_t room, ClientSocketEvent *evt) {
|
||||||
|
if(room < SOCK_MAX_ROOMS) {
|
||||||
|
room_t *r = &this->rooms[room];
|
||||||
|
for(uint8_t i = 0; i < sizeof(r->clients); i++) {
|
||||||
|
if(r->clients[i] != 255) this->sendToClient(r->clients[i], evt);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool SocketEmitter::sendToClients(ClientSocketEvent *evt) { return sockServer.broadcastTXT(evt->msg); }
|
||||||
|
bool SocketEmitter::sendToClient(uint8_t num, ClientSocketEvent *evt) { return sockServer.sendTXT(num, evt->msg); }
|
||||||
bool SocketEmitter::sendToClients(const char *evt, const char *payload) {
|
bool SocketEmitter::sendToClients(const char *evt, const char *payload) {
|
||||||
if(settings.status == DS_FWUPDATE) return true;
|
if(settings.status == DS_FWUPDATE) return true;
|
||||||
this->evt.prepareMessage(evt, payload);
|
this->evt.prepareMessage(evt, payload);
|
||||||
|
|
@ -71,6 +126,9 @@ void SocketEmitter::wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t
|
||||||
Serial.printf("Socket [%u] Disconnected!\n [%s]", num, payload);
|
Serial.printf("Socket [%u] Disconnected!\n [%s]", num, payload);
|
||||||
else
|
else
|
||||||
Serial.printf("Socket [%u] Disconnected!\n", num);
|
Serial.printf("Socket [%u] Disconnected!\n", num);
|
||||||
|
for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++) {
|
||||||
|
sockEmit.rooms[i].leave(num);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case WStype_CONNECTED:
|
case WStype_CONNECTED:
|
||||||
{
|
{
|
||||||
|
|
@ -84,8 +142,21 @@ void SocketEmitter::wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case WStype_TEXT:
|
case WStype_TEXT:
|
||||||
Serial.printf("Socket [%u] get Text: %s\n", num, payload);
|
if(strncmp((char *)payload, "join:", 5) == 0) {
|
||||||
|
// In this instance the client wants to join a room. Let's do some
|
||||||
|
// work to get the ordinal of the room that the client wants to join.
|
||||||
|
uint8_t roomNum = atoi((char *)&payload[5]);
|
||||||
|
Serial.printf("Client %u joining room %u\n", num, roomNum);
|
||||||
|
if(roomNum < SOCK_MAX_ROOMS) sockEmit.rooms[roomNum].join(num);
|
||||||
|
}
|
||||||
|
else if(strncmp((char *)payload, "leave:", 6) == 0) {
|
||||||
|
uint8_t roomNum = atoi((char *)&payload[6]);
|
||||||
|
Serial.printf("Client %u leaving room %u\n", num, roomNum);
|
||||||
|
if(roomNum < SOCK_MAX_ROOMS) sockEmit.rooms[roomNum].leave(num);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Serial.printf("Socket [%u] text: %s\n", num, payload);
|
||||||
|
}
|
||||||
// send message to client
|
// send message to client
|
||||||
// webSocket.sendTXT(num, "message here");
|
// webSocket.sendTXT(num, "message here");
|
||||||
|
|
||||||
|
|
|
||||||
26
Sockets.h
26
Sockets.h
|
|
@ -2,19 +2,43 @@
|
||||||
#ifndef sockets_h
|
#ifndef sockets_h
|
||||||
#define sockets_h
|
#define sockets_h
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
#define SOCK_MAX_ROOMS 1
|
||||||
|
#define ROOM_EMIT_FRAME 0
|
||||||
|
|
||||||
|
typedef struct room_t {
|
||||||
|
uint8_t clients[5] = {255, 255, 255, 255};
|
||||||
|
uint8_t activeClients();
|
||||||
|
bool isJoined(uint8_t num);
|
||||||
|
bool join(uint8_t num);
|
||||||
|
bool leave(uint8_t num);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class ClientSocketEvent {
|
class ClientSocketEvent {
|
||||||
public:
|
public:
|
||||||
char msg[1024];
|
ClientSocketEvent();
|
||||||
|
ClientSocketEvent(const char *evt);
|
||||||
|
ClientSocketEvent(const char *evt, const char *data);
|
||||||
|
char msg[2048];
|
||||||
void prepareMessage(const char *evt, const char *data);
|
void prepareMessage(const char *evt, const char *data);
|
||||||
|
void appendMessage(const char *text);
|
||||||
|
|
||||||
};
|
};
|
||||||
class SocketEmitter {
|
class SocketEmitter {
|
||||||
ClientSocketEvent evt;
|
ClientSocketEvent evt;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
room_t rooms[SOCK_MAX_ROOMS];
|
||||||
|
uint8_t activeClients(uint8_t room);
|
||||||
void startup();
|
void startup();
|
||||||
void begin();
|
void begin();
|
||||||
void loop();
|
void loop();
|
||||||
void end();
|
void end();
|
||||||
void disconnect();
|
void disconnect();
|
||||||
|
bool sendToRoom(uint8_t room, ClientSocketEvent *evt);
|
||||||
|
bool sendToClients(ClientSocketEvent *evt);
|
||||||
|
bool sendToClient(uint8_t num, ClientSocketEvent *evt);
|
||||||
bool sendToClients(const char *evt, const char *data);
|
bool sendToClients(const char *evt, const char *data);
|
||||||
bool sendToClient(uint8_t num, const char *evt, const char *data);
|
bool sendToClient(uint8_t num, const char *evt, const char *data);
|
||||||
//bool sendToClients(const char *evt, JsonObject &obj);
|
//bool sendToClients(const char *evt, JsonObject &obj);
|
||||||
|
|
|
||||||
65
Somfy.cpp
65
Somfy.cpp
|
|
@ -47,10 +47,14 @@ somfy_commands translateSomfyCommand(const String& string) {
|
||||||
else if (string.equalsIgnoreCase("UpDown")) return somfy_commands::UpDown;
|
else if (string.equalsIgnoreCase("UpDown")) return somfy_commands::UpDown;
|
||||||
else if (string.equalsIgnoreCase("Prog")) return somfy_commands::Prog;
|
else if (string.equalsIgnoreCase("Prog")) return somfy_commands::Prog;
|
||||||
else if (string.equalsIgnoreCase("SunFlag")) return somfy_commands::SunFlag;
|
else if (string.equalsIgnoreCase("SunFlag")) return somfy_commands::SunFlag;
|
||||||
|
else if (string.equalsIgnoreCase("StepUp")) return somfy_commands::StepUp;
|
||||||
|
else if (string.equalsIgnoreCase("StepDown")) return somfy_commands::StepDown;
|
||||||
else if (string.equalsIgnoreCase("Flag")) return somfy_commands::Flag;
|
else if (string.equalsIgnoreCase("Flag")) return somfy_commands::Flag;
|
||||||
else if (string.startsWith("md") || string.startsWith("MD")) return somfy_commands::MyDown;
|
else if (string.startsWith("md") || string.startsWith("MD")) return somfy_commands::MyDown;
|
||||||
else if (string.startsWith("ud") || string.startsWith("UD")) return somfy_commands::UpDown;
|
else if (string.startsWith("ud") || string.startsWith("UD")) return somfy_commands::UpDown;
|
||||||
else if (string.startsWith("mu") || string.startsWith("MU")) return somfy_commands::MyUp;
|
else if (string.startsWith("mu") || string.startsWith("MU")) return somfy_commands::MyUp;
|
||||||
|
else if (string.startsWith("su") || string.startsWith("SU")) return somfy_commands::StepUp;
|
||||||
|
else if (string.startsWith("sd") || string.startsWith("SD")) return somfy_commands::StepDown;
|
||||||
else if (string.startsWith("p") || string.startsWith("P")) return somfy_commands::Prog;
|
else if (string.startsWith("p") || string.startsWith("P")) return somfy_commands::Prog;
|
||||||
else if (string.startsWith("u") || string.startsWith("U")) return somfy_commands::Up;
|
else if (string.startsWith("u") || string.startsWith("U")) return somfy_commands::Up;
|
||||||
else if (string.startsWith("d") || string.startsWith("D")) return somfy_commands::Down;
|
else if (string.startsWith("d") || string.startsWith("D")) return somfy_commands::Down;
|
||||||
|
|
@ -80,9 +84,12 @@ String translateSomfyCommand(const somfy_commands cmd) {
|
||||||
return "Sun Flag";
|
return "Sun Flag";
|
||||||
case somfy_commands::Flag:
|
case somfy_commands::Flag:
|
||||||
return "Flag";
|
return "Flag";
|
||||||
|
case somfy_commands::StepUp:
|
||||||
|
return "Step Up";
|
||||||
|
case somfy_commands::StepDown:
|
||||||
|
return "Step Down";
|
||||||
default:
|
default:
|
||||||
return "Unknown(" + String((uint8_t)cmd) + ")";
|
return "Unknown(" + String((uint8_t)cmd) + ")";
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void somfy_frame_t::decodeFrame(byte* frame) {
|
void somfy_frame_t::decodeFrame(byte* frame) {
|
||||||
|
|
@ -108,6 +115,7 @@ void somfy_frame_t::decodeFrame(byte* frame) {
|
||||||
if (this->valid) {
|
if (this->valid) {
|
||||||
// Check for valid command.
|
// Check for valid command.
|
||||||
switch (this->cmd) {
|
switch (this->cmd) {
|
||||||
|
case somfy_commands::Unknown0:
|
||||||
case somfy_commands::My:
|
case somfy_commands::My:
|
||||||
case somfy_commands::Up:
|
case somfy_commands::Up:
|
||||||
case somfy_commands::MyUp:
|
case somfy_commands::MyUp:
|
||||||
|
|
@ -117,6 +125,12 @@ void somfy_frame_t::decodeFrame(byte* frame) {
|
||||||
case somfy_commands::Prog:
|
case somfy_commands::Prog:
|
||||||
case somfy_commands::SunFlag:
|
case somfy_commands::SunFlag:
|
||||||
case somfy_commands::Flag:
|
case somfy_commands::Flag:
|
||||||
|
case somfy_commands::StepUp:
|
||||||
|
case somfy_commands::StepDown:
|
||||||
|
case somfy_commands::Unknown7:
|
||||||
|
case somfy_commands::UnknownD:
|
||||||
|
case somfy_commands::UnknownE:
|
||||||
|
case somfy_commands::UnknownF:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this->valid = false;
|
this->valid = false;
|
||||||
|
|
@ -1470,7 +1484,9 @@ typedef enum {
|
||||||
receiving_data = 1,
|
receiving_data = 1,
|
||||||
complete = 2
|
complete = 2
|
||||||
} t_status;
|
} t_status;
|
||||||
|
#define MAX_TIMINGS 500
|
||||||
|
static uint16_t timing_index = 0;
|
||||||
|
static uint32_t timings[MAX_TIMINGS];
|
||||||
static struct somfy_rx_t
|
static struct somfy_rx_t
|
||||||
{
|
{
|
||||||
t_status status;
|
t_status status;
|
||||||
|
|
@ -1479,6 +1495,8 @@ static struct somfy_rx_t
|
||||||
uint8_t previous_bit;
|
uint8_t previous_bit;
|
||||||
bool waiting_half_symbol;
|
bool waiting_half_symbol;
|
||||||
uint8_t payload[10];
|
uint8_t payload[10];
|
||||||
|
unsigned int pulses[MAX_TIMINGS];
|
||||||
|
uint16_t pulseCount;
|
||||||
} somfy_rx;
|
} somfy_rx;
|
||||||
uint8_t receive_buffer[10]; // 80 bits
|
uint8_t receive_buffer[10]; // 80 bits
|
||||||
bool packet_received = false;
|
bool packet_received = false;
|
||||||
|
|
@ -1529,6 +1547,7 @@ void Transceiver::sendFrame(byte *frame, uint8_t sync) {
|
||||||
REG_WRITE(GPIO_OUT_W1TC_REG, pin);
|
REG_WRITE(GPIO_OUT_W1TC_REG, pin);
|
||||||
delayMicroseconds(30415);
|
delayMicroseconds(30415);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RECEIVE_ATTR Transceiver::handleReceive() {
|
void RECEIVE_ATTR Transceiver::handleReceive() {
|
||||||
static unsigned long last_time = 0;
|
static unsigned long last_time = 0;
|
||||||
const long time = micros();
|
const long time = micros();
|
||||||
|
|
@ -1541,6 +1560,7 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
|
||||||
last_time = time;
|
last_time = time;
|
||||||
switch (somfy_rx.status) {
|
switch (somfy_rx.status) {
|
||||||
case waiting_synchro:
|
case waiting_synchro:
|
||||||
|
somfy_rx.pulses[somfy_rx.pulseCount++] = duration;
|
||||||
if (duration > tempo_synchro_hw_min && duration < tempo_synchro_hw_max) {
|
if (duration > tempo_synchro_hw_min && duration < tempo_synchro_hw_max) {
|
||||||
// We have found a hardware sync bit. There should be at least 4 of these.
|
// We have found a hardware sync bit. There should be at least 4 of these.
|
||||||
++somfy_rx.cpt_synchro_hw;
|
++somfy_rx.cpt_synchro_hw;
|
||||||
|
|
@ -1554,14 +1574,15 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
|
||||||
somfy_rx.waiting_half_symbol = false;
|
somfy_rx.waiting_half_symbol = false;
|
||||||
somfy_rx.cpt_bits = 0;
|
somfy_rx.cpt_bits = 0;
|
||||||
somfy_rx.status = receiving_data;
|
somfy_rx.status = receiving_data;
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Reset and start looking for hardware sync again.
|
// Reset and start looking for hardware sync again.
|
||||||
somfy_rx.cpt_synchro_hw = 0;
|
somfy_rx.cpt_synchro_hw = 0;
|
||||||
|
somfy_rx.pulseCount = 0;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case receiving_data:
|
case receiving_data:
|
||||||
|
somfy_rx.pulses[somfy_rx.pulseCount++] = duration;
|
||||||
// We should be receiving data at this point.
|
// We should be receiving data at this point.
|
||||||
if (duration > tempo_symbol_min && duration < tempo_symbol_max && !somfy_rx.waiting_half_symbol) {
|
if (duration > tempo_symbol_min && duration < tempo_symbol_max && !somfy_rx.waiting_half_symbol) {
|
||||||
somfy_rx.previous_bit = 1 - somfy_rx.previous_bit;
|
somfy_rx.previous_bit = 1 - somfy_rx.previous_bit;
|
||||||
|
|
@ -1582,6 +1603,7 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
|
||||||
else {
|
else {
|
||||||
// Start over we are not within our parameters for bit timing.
|
// Start over we are not within our parameters for bit timing.
|
||||||
memset(&somfy_rx.payload, 0x00, sizeof(somfy_rx.payload));
|
memset(&somfy_rx.payload, 0x00, sizeof(somfy_rx.payload));
|
||||||
|
somfy_rx.pulseCount = 0;
|
||||||
somfy_rx.cpt_synchro_hw = 0;
|
somfy_rx.cpt_synchro_hw = 0;
|
||||||
somfy_rx.previous_bit = 0x00;
|
somfy_rx.previous_bit = 0x00;
|
||||||
somfy_rx.waiting_half_symbol = false;
|
somfy_rx.waiting_half_symbol = false;
|
||||||
|
|
@ -1593,6 +1615,8 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (somfy_rx.status == receiving_data && somfy_rx.cpt_bits == bit_length) {
|
if (somfy_rx.status == receiving_data && somfy_rx.cpt_bits == bit_length) {
|
||||||
|
timing_index = somfy_rx.pulseCount;
|
||||||
|
memcpy(timings, somfy_rx.pulses, somfy_rx.pulseCount * sizeof(uint32_t));
|
||||||
memcpy(receive_buffer, somfy_rx.payload, sizeof(receive_buffer));
|
memcpy(receive_buffer, somfy_rx.payload, sizeof(receive_buffer));
|
||||||
packet_received = true;
|
packet_received = true;
|
||||||
m_hwsync = somfy_rx.cpt_synchro_hw;
|
m_hwsync = somfy_rx.cpt_synchro_hw;
|
||||||
|
|
@ -1612,10 +1636,35 @@ bool Transceiver::receive() {
|
||||||
this->frame.decodeFrame(receive_buffer);
|
this->frame.decodeFrame(receive_buffer);
|
||||||
//this->frame.lqi = ELECHOUSE_cc1101.getLqi();
|
//this->frame.lqi = ELECHOUSE_cc1101.getLqi();
|
||||||
if (!this->frame.valid) this->clearReceived();
|
if (!this->frame.valid) this->clearReceived();
|
||||||
|
this->emitFrame(&this->frame);
|
||||||
return this->frame.valid;
|
return this->frame.valid;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
void Transceiver::emitFrame(somfy_frame_t *frame) {
|
||||||
|
if(sockEmit.activeClients(ROOM_EMIT_FRAME) > 0) {
|
||||||
|
ClientSocketEvent evt("remoteFrame");
|
||||||
|
char buf[30];
|
||||||
|
snprintf(buf, sizeof(buf), "{\"encKey\":%d,", frame->encKey);
|
||||||
|
evt.appendMessage(buf);
|
||||||
|
snprintf(buf, sizeof(buf), "\"address\":%d,", frame->remoteAddress);
|
||||||
|
evt.appendMessage(buf);
|
||||||
|
snprintf(buf, sizeof(buf), "\"rcode\":%d,", frame->rollingCode);
|
||||||
|
evt.appendMessage(buf);
|
||||||
|
snprintf(buf, sizeof(buf), "\"command\":\"%s\",", translateSomfyCommand(frame->cmd));
|
||||||
|
evt.appendMessage(buf);
|
||||||
|
snprintf(buf, sizeof(buf), "\"rssi\":%d,", frame->rssi);
|
||||||
|
evt.appendMessage(buf);
|
||||||
|
snprintf(buf, sizeof(buf), "\"sync\":%d,\"pulses\":[", frame->hwsync);
|
||||||
|
evt.appendMessage(buf);
|
||||||
|
for(uint16_t i = 0; i < timing_index; i++) {
|
||||||
|
snprintf(buf, sizeof(buf), "%s%d", i != 0 ? "," : "", timings[i]);
|
||||||
|
evt.appendMessage(buf);
|
||||||
|
}
|
||||||
|
evt.appendMessage("]}");
|
||||||
|
sockEmit.sendToRoom(ROOM_EMIT_FRAME, &evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
void Transceiver::clearReceived(void) {
|
void Transceiver::clearReceived(void) {
|
||||||
packet_received = false;
|
packet_received = false;
|
||||||
memset(receive_buffer, 0x00, sizeof(receive_buffer));
|
memset(receive_buffer, 0x00, sizeof(receive_buffer));
|
||||||
|
|
@ -1854,16 +1903,19 @@ void transceiver_config_t::apply() {
|
||||||
if(!radioInit) return;
|
if(!radioInit) return;
|
||||||
Serial.print("Applying radio settings ");
|
Serial.print("Applying radio settings ");
|
||||||
Serial.printf("Setting Data Pins RX:%u TX:%u\n", this->RXPin, this->TXPin);
|
Serial.printf("Setting Data Pins RX:%u TX:%u\n", this->RXPin, this->TXPin);
|
||||||
ELECHOUSE_cc1101.setGDO(this->TXPin, this->RXPin);
|
if(this->TXPin == this->RXPin)
|
||||||
|
ELECHOUSE_cc1101.setGDO0(this->TXPin); // This pin may be shared.
|
||||||
|
else
|
||||||
|
ELECHOUSE_cc1101.setGDO(this->TXPin, this->RXPin); // GDO0, GDO2
|
||||||
Serial.printf("Setting SPI Pins SCK:%u MISO:%u MOSI:%u CSN:%u\n", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin);
|
Serial.printf("Setting SPI Pins SCK:%u MISO:%u MOSI:%u CSN:%u\n", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin);
|
||||||
ELECHOUSE_cc1101.setSpiPin(this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin);
|
ELECHOUSE_cc1101.setSpiPin(this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin);
|
||||||
Serial.println("Radio Pins Configured!");
|
Serial.println("Radio Pins Configured!");
|
||||||
ELECHOUSE_cc1101.Init();
|
ELECHOUSE_cc1101.Init();
|
||||||
|
ELECHOUSE_cc1101.setCCMode(0); // set config for internal transmission mode.
|
||||||
ELECHOUSE_cc1101.setMHZ(this->frequency); // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet.
|
ELECHOUSE_cc1101.setMHZ(this->frequency); // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet.
|
||||||
ELECHOUSE_cc1101.setRxBW(this->rxBandwidth); // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz.
|
ELECHOUSE_cc1101.setRxBW(this->rxBandwidth); // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz.
|
||||||
ELECHOUSE_cc1101.setDeviation(this->deviation);
|
ELECHOUSE_cc1101.setDeviation(this->deviation);
|
||||||
ELECHOUSE_cc1101.setPA(this->txPower); // Set TxPower. The following settings are possible depending on the frequency band. (-30 -20 -15 -10 -6 0 5 7 10 11 12) Default is max!
|
ELECHOUSE_cc1101.setPA(this->txPower); // Set TxPower. The following settings are possible depending on the frequency band. (-30 -20 -15 -10 -6 0 5 7 10 11 12) Default is max!
|
||||||
//ELECHOUSE_cc1101.setCCMode(this->internalCCMode); // set config for internal transmission mode.
|
|
||||||
//ELECHOUSE_cc1101.setModulation(this->modulationMode); // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
|
//ELECHOUSE_cc1101.setModulation(this->modulationMode); // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
|
||||||
if (!ELECHOUSE_cc1101.getCC1101()) {
|
if (!ELECHOUSE_cc1101.getCC1101()) {
|
||||||
Serial.println("Error setting up the radio");
|
Serial.println("Error setting up the radio");
|
||||||
|
|
@ -1885,7 +1937,6 @@ void transceiver_config_t::apply() {
|
||||||
this->radioInit = false;
|
this->radioInit = false;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
ELECHOUSE_cc1101.setDeviation(this->deviation); // Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz.
|
|
||||||
ELECHOUSE_cc1101.setChannel(this->channel); // Set the Channelnumber from 0 to 255. Default is cahnnel 0.
|
ELECHOUSE_cc1101.setChannel(this->channel); // Set the Channelnumber from 0 to 255. Default is cahnnel 0.
|
||||||
ELECHOUSE_cc1101.setChsp(this->channelSpacing); // The channel spacing is multiplied by the channel number CHAN and added to the base frequency in kHz. Value from 25.39 to 405.45. Default is 199.95 kHz.
|
ELECHOUSE_cc1101.setChsp(this->channelSpacing); // The channel spacing is multiplied by the channel number CHAN and added to the base frequency in kHz. Value from 25.39 to 405.45. Default is 199.95 kHz.
|
||||||
ELECHOUSE_cc1101.setDRate(this->dataRate); // Set the Data Rate in kBaud. Value from 0.02 to 1621.83. Default is 99.97 kBaud!
|
ELECHOUSE_cc1101.setDRate(this->dataRate); // Set the Data Rate in kBaud. Value from 0.02 to 1621.83. Default is 99.97 kBaud!
|
||||||
|
|
@ -1917,9 +1968,11 @@ void Transceiver::loop() {
|
||||||
if (this->receive()) {
|
if (this->receive()) {
|
||||||
this->clearReceived();
|
this->clearReceived();
|
||||||
somfy.processFrame(this->frame, false);
|
somfy.processFrame(this->frame, false);
|
||||||
|
/*
|
||||||
char buf[177];
|
char buf[177];
|
||||||
snprintf(buf, sizeof(buf), "{\"encKey\":%d,\"address\":%d,\"rcode\":%d,\"command\":\"%s\",\"rssi\":%d}", this->frame.encKey, this->frame.remoteAddress, this->frame.rollingCode, translateSomfyCommand(this->frame.cmd), this->frame.rssi);
|
snprintf(buf, sizeof(buf), "{\"encKey\":%d,\"address\":%d,\"rcode\":%d,\"command\":\"%s\",\"rssi\":%d}", this->frame.encKey, this->frame.remoteAddress, this->frame.rollingCode, translateSomfyCommand(this->frame.cmd), this->frame.rssi);
|
||||||
sockEmit.sendToClients("remoteFrame", buf);
|
sockEmit.sendToClients("remoteFrame", buf);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
somfy.processWaitingFrame();
|
somfy.processWaitingFrame();
|
||||||
|
|
|
||||||
10
Somfy.h
10
Somfy.h
|
|
@ -11,15 +11,22 @@ typedef struct appver_t {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class somfy_commands : byte {
|
enum class somfy_commands : byte {
|
||||||
|
Unknown0 = 0x0,
|
||||||
My = 0x1,
|
My = 0x1,
|
||||||
Up = 0x2,
|
Up = 0x2,
|
||||||
MyUp = 0x3,
|
MyUp = 0x3,
|
||||||
Down = 0x4,
|
Down = 0x4,
|
||||||
MyDown = 0x5,
|
MyDown = 0x5,
|
||||||
UpDown = 0x6,
|
UpDown = 0x6,
|
||||||
|
Unknown7 = 0x7,
|
||||||
Prog = 0x8,
|
Prog = 0x8,
|
||||||
SunFlag = 0x9,
|
SunFlag = 0x9,
|
||||||
Flag = 0xA
|
Flag = 0xA,
|
||||||
|
StepUp = 0xB,
|
||||||
|
StepDown = 0xC,
|
||||||
|
UnknownD = 0xD,
|
||||||
|
UnknownE = 0xE,
|
||||||
|
UnknownF = 0xF,
|
||||||
};
|
};
|
||||||
enum class shade_types : byte {
|
enum class shade_types : byte {
|
||||||
roller = 0x00,
|
roller = 0x00,
|
||||||
|
|
@ -227,6 +234,7 @@ class Transceiver {
|
||||||
void sendFrame(byte *frame, uint8_t sync);
|
void sendFrame(byte *frame, uint8_t sync);
|
||||||
void beginTransmit();
|
void beginTransmit();
|
||||||
void endTransmit();
|
void endTransmit();
|
||||||
|
void emitFrame(somfy_frame_t *frame);
|
||||||
};
|
};
|
||||||
class SomfyShadeController {
|
class SomfyShadeController {
|
||||||
protected:
|
protected:
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -664,3 +664,29 @@ i.icss-file {
|
||||||
transform: rotate(-45deg);
|
transform: rotate(-45deg);
|
||||||
top: .02em;
|
top: .02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i.icss-x {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
background-color: transparent;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.icss-x:before,
|
||||||
|
i.icss-x:after {
|
||||||
|
width: 1em;
|
||||||
|
height: .18em;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
border-radius: .06em;
|
||||||
|
background-color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.icss-x:before {
|
||||||
|
transform: translate(-50%, -50%) rotate(-225deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
i.icss-x:after {
|
||||||
|
transform: translate(-50%, -50%) rotate(225deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="stylesheet" href="main.css?v=1.4.1" type="text/css" />
|
<link rel="stylesheet" href="main.css?v=1.4.1" type="text/css" />
|
||||||
<link rel="stylesheet" href="icons.css?v=1.4.0" type="text/css" />
|
<link rel="stylesheet" href="icons.css?v=1.4.1" type="text/css" />
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<script type="text/javascript" src="index.js?v=1.4.0"></script>
|
<script type="text/javascript" src="index.js?v=1.4.1"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="divContainer" class="container" style="user-select:none;position:relative;border-radius:27px;">
|
<div id="divContainer" class="container" style="user-select:none;position:relative;border-radius:27px;">
|
||||||
|
|
@ -200,7 +200,7 @@
|
||||||
<form method="post" action="/radioSettings">
|
<form method="post" action="/radioSettings">
|
||||||
<hr />
|
<hr />
|
||||||
<div id="divShadeSection">
|
<div id="divShadeSection">
|
||||||
<div id="divShadeList" style="overflow-y: auto; max-height: 400px; padding-top: 2px; padding-bottom: 2px;"></div>
|
<div id="divShadeList" style="overflow-y: auto; max-height: 400px; padding-top: 2px; padding-bottom: 2px;min-height:127px;"></div>
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
<button id="btnAddShade" type="button" onclick="somfy.openEditShade();">
|
<button id="btnAddShade" type="button" onclick="somfy.openEditShade();">
|
||||||
Add Shade
|
Add Shade
|
||||||
|
|
@ -245,11 +245,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="field-group" style="display:inline-block;max-width:127px;margin-right:17px;">
|
<div class="field-group" style="display:inline-block;max-width:127px;margin-right:17px;">
|
||||||
<input id="fldShadeUpTime" name="shadeUpTime" type="number" length=5 placeholder="milliseconds" style="width:100%;text-align:right;"/>
|
<input id="fldShadeUpTime" name="shadeUpTime" type="number" length=5 placeholder="milliseconds" style="width:100%;text-align:right;" />
|
||||||
<label for="fldShadeUpTime">Up Time (ms)</label>
|
<label for="fldShadeUpTime">Up Time (ms)</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-group" style="display:inline-block;max-width:127px;">
|
<div class="field-group" style="display:inline-block;max-width:127px;">
|
||||||
<input id="fldShadeDownTime" name="shadeDownTime" type="number" length=5 placeholder="milliseconds" style="width:100%;text-align:right;"/>
|
<input id="fldShadeDownTime" name="shadeDownTime" type="number" length=5 placeholder="milliseconds" style="width:100%;text-align:right;" />
|
||||||
<label for="fldShadeDownTime">Down Time (ms)</label>
|
<label for="fldShadeDownTime">Down Time (ms)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -291,7 +291,7 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="somfyTransceiver" style="display:none;">
|
<div id="somfyTransceiver" style="display:none;position:relative;">
|
||||||
<form>
|
<form>
|
||||||
<div id="divRadioSettings" name="divRadioSettings" class="field-group1" style="display:block;position:relative">
|
<div id="divRadioSettings" name="divRadioSettings" class="field-group1" style="display:block;position:relative">
|
||||||
<div class="field-group" style="">
|
<div class="field-group" style="">
|
||||||
|
|
@ -359,6 +359,7 @@
|
||||||
<label style="display:block;width:100%;text-align:center;">RSSI: <span id="spanFrameCount" style="color:silver;">0</span></label>
|
<label style="display:block;width:100%;text-align:center;">RSSI: <span id="spanFrameCount" style="color:silver;">0</span></label>
|
||||||
<span id="spanRssi" name="rssi" style="display:block;font-size:32px;width:100%;text-align:center;">---</span>
|
<span id="spanRssi" name="rssi" style="display:block;font-size:32px;width:100%;text-align:center;">---</span>
|
||||||
<span style="display: block; color: #00bcd4;width:100%;text-align:center;">dBm</span>
|
<span style="display: block; color: #00bcd4;width:100%;text-align:center;">dBm</span>
|
||||||
|
<button id="btnReceiveLog" type="button" onclick="document.getElementById('divFrameLog').style.display = '';" style="padding-left:7px;padding-right:7px;">Logs</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -414,6 +415,17 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="divFrameLog" class="frame-log" style="display:none;">
|
||||||
|
<h1 style="text-align: center;padding:10px;"><span>Frame Logs</span><span class="button-outline" onclick="document.getElementById('divFrameLog').style.display = 'none';" style="float:right;font-size:1.25rem;display:inline-block;vertical-align:middle;width:38px;height:38px;position:relative;padding-top:4px;"><span style="vertical-align:middle;clear:both;text-align:center;display:inline-block;"><i id="icoConfig" class="icss-x" style=""></i></span></span></h1>
|
||||||
|
<hr style="margin:0px;" />
|
||||||
|
<div class="frame-header"><span>Key</span><span>Address</span><span>Command</span><span>Code</span><span>RSSI</span></div>
|
||||||
|
<div id="divFrames" class="frame-list"></div>
|
||||||
|
<div class="button-container" style="text-align:center">
|
||||||
|
<button type="button" class="btnCopyFrame" style="display:inline-block;width:44%;" onclick="somfy.framesToClipboard();">Copy</button>
|
||||||
|
<button type="button" class="btnClearFrames" style="display:inline-block;width:44%;" onclick="document.getElementById('divFrames').innerHTML = ''; somfy.frames = [];">Clear</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
waitMessage(document.getElementById('divShadeControls'));
|
waitMessage(document.getElementById('divShadeControls'));
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,9 @@ async function initSockets() {
|
||||||
case 'wifiStrength':
|
case 'wifiStrength':
|
||||||
wifi.procWifiStrength(msg);
|
wifi.procWifiStrength(msg);
|
||||||
break;
|
break;
|
||||||
|
case 'packetPulses':
|
||||||
|
console.log(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -369,12 +372,13 @@ async function reopenSocket() {
|
||||||
await initSockets();
|
await initSockets();
|
||||||
}
|
}
|
||||||
class General {
|
class General {
|
||||||
appVersion = 'v1.4.0';
|
appVersion = 'v1.4.1';
|
||||||
reloadApp = false;
|
reloadApp = false;
|
||||||
async init() {
|
async init() {
|
||||||
this.setAppVersion();
|
this.setAppVersion();
|
||||||
this.setTimeZones();
|
this.setTimeZones();
|
||||||
this.loadGeneral();
|
this.loadGeneral();
|
||||||
|
if (sockIsOpen && this.isConfigOpen()) socket.send('join:0');
|
||||||
};
|
};
|
||||||
reload() {
|
reload() {
|
||||||
let addMetaTag = (name, content) => {
|
let addMetaTag = (name, content) => {
|
||||||
|
|
@ -573,6 +577,10 @@ class General {
|
||||||
clearErrors();
|
clearErrors();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
isConfigOpen() {
|
||||||
|
let divCfg = document.getElementById('divConfigPnl');
|
||||||
|
return (window.getComputedStyle(divCfg).display !== 'none');
|
||||||
|
};
|
||||||
toggleConfig() {
|
toggleConfig() {
|
||||||
let divCfg = document.getElementById('divConfigPnl');
|
let divCfg = document.getElementById('divConfigPnl');
|
||||||
let divHome = document.getElementById('divHomePnl');
|
let divHome = document.getElementById('divHomePnl');
|
||||||
|
|
@ -580,15 +588,16 @@ class General {
|
||||||
divHome.style.display = 'none';
|
divHome.style.display = 'none';
|
||||||
divCfg.style.display = '';
|
divCfg.style.display = '';
|
||||||
document.getElementById('icoConfig').className = 'icss-home';
|
document.getElementById('icoConfig').className = 'icss-home';
|
||||||
|
if (sockIsOpen) socket.send('join:0');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
divHome.style.display = '';
|
divHome.style.display = '';
|
||||||
divCfg.style.display = 'none';
|
divCfg.style.display = 'none';
|
||||||
document.getElementById('icoConfig').className = 'icss-gear';
|
document.getElementById('icoConfig').className = 'icss-gear';
|
||||||
|
if (sockIsOpen) socket.send('leave:0');
|
||||||
}
|
}
|
||||||
somfy.closeEditShade();
|
somfy.closeEditShade();
|
||||||
somfy.closeConfigTransceiver();
|
somfy.closeConfigTransceiver();
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
var general = new General();
|
var general = new General();
|
||||||
|
|
@ -881,6 +890,7 @@ class Wifi {
|
||||||
};
|
};
|
||||||
var wifi = new Wifi();
|
var wifi = new Wifi();
|
||||||
class Somfy {
|
class Somfy {
|
||||||
|
frames = [];
|
||||||
async init() {
|
async init() {
|
||||||
this.loadPins('inout', document.getElementById('selTransSCKPin'));
|
this.loadPins('inout', document.getElementById('selTransSCKPin'));
|
||||||
this.loadPins('inout', document.getElementById('selTransCSNPin'));
|
this.loadPins('inout', document.getElementById('selTransCSNPin'));
|
||||||
|
|
@ -1287,7 +1297,54 @@ class Somfy {
|
||||||
this.setLinkedRemotesList(shade);
|
this.setLinkedRemotesList(shade);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let frames = document.getElementById('divFrames');
|
||||||
|
let row = document.createElement('div');
|
||||||
|
row.classList.add('frame-row');
|
||||||
|
let html = `<span>${frame.encKey}</span><span>${frame.address}</span><span>${frame.command}</span><span>${frame.rcode}</span><span>${frame.rssi}dBm</span><div class="frame-pulses">`;
|
||||||
|
for (let i = 0; i < frame.pulses.length; i++) {
|
||||||
|
if (i !== 0) html += ',';
|
||||||
|
html += `${frame.pulses[i]}`;
|
||||||
|
}
|
||||||
|
row.innerHTML = html;
|
||||||
|
frames.prepend(row);
|
||||||
|
this.frames.push(frame);
|
||||||
};
|
};
|
||||||
|
JSONPretty(obj, indent = 2) {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
let output = '[';
|
||||||
|
for (let i = 0; i < obj.length; i++) {
|
||||||
|
if (i != 0) output += ',\n';
|
||||||
|
output += this.JSONPretty(obj[i], indent);
|
||||||
|
}
|
||||||
|
output += ']'
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let output = JSON.stringify(obj, function (k, v) {
|
||||||
|
if (Array.isArray(v)) return JSON.stringify(v);
|
||||||
|
return v;
|
||||||
|
}, indent).replace(/\\/g, '')
|
||||||
|
.replace(/\"\[/g, '[')
|
||||||
|
.replace(/\]\"/g, ']')
|
||||||
|
.replace(/\"\{/g, '{')
|
||||||
|
.replace(/\}\"/g, '}')
|
||||||
|
.replace(/\{\n\s+/g, '{');
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
framesToClipboard() {
|
||||||
|
if (typeof navigator.clipboard !== 'undefined')
|
||||||
|
navigator.clipboard.writeText(this.JSONPretty(this.frames, 2));
|
||||||
|
else {
|
||||||
|
let dummy = document.createElement('textarea');
|
||||||
|
document.body.appendChild(dummy);
|
||||||
|
dummy.value = this.JSONPretty(this.frames, 2);
|
||||||
|
dummy.focus();
|
||||||
|
dummy.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(dummy);
|
||||||
|
}
|
||||||
|
}
|
||||||
openConfigTransceiver() {
|
openConfigTransceiver() {
|
||||||
document.getElementById('somfyMain').style.display = 'none';
|
document.getElementById('somfyMain').style.display = 'none';
|
||||||
document.getElementById('somfyTransceiver').style.display = '';
|
document.getElementById('somfyTransceiver').style.display = '';
|
||||||
|
|
|
||||||
|
|
@ -663,5 +663,83 @@ div.eth-setting-line label {
|
||||||
width: 90px;
|
width: 90px;
|
||||||
text-align:right;
|
text-align:right;
|
||||||
color:mediumspringgreen;
|
color:mediumspringgreen;
|
||||||
|
}
|
||||||
|
div.frame-log {
|
||||||
|
position:absolute;
|
||||||
|
top:0px;
|
||||||
|
left:0px;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
border-radius:27px;
|
||||||
|
border:solid 2px gray;
|
||||||
|
background:gainsboro;
|
||||||
|
}
|
||||||
|
div.frame-list {
|
||||||
|
position:relative;
|
||||||
|
width: calc(100% - 14px);
|
||||||
|
height: calc(100% - 147px);
|
||||||
|
left: 7px;
|
||||||
|
background:white;
|
||||||
|
overflow-y:auto;
|
||||||
|
border:solid 1px silver;
|
||||||
|
}
|
||||||
|
div.frame-header {
|
||||||
|
position: relative;
|
||||||
|
font-size: 12px;
|
||||||
|
background: #00bcd4;
|
||||||
|
width: calc(100% - 14px);
|
||||||
|
left: 7px;
|
||||||
|
color:white;
|
||||||
|
padding-top:4px;
|
||||||
|
}
|
||||||
|
div.frame-row > span,
|
||||||
|
div.frame-header > span {
|
||||||
|
overflow:hidden;
|
||||||
|
display:inline-block;
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
div.frame-header > span {
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
div.frame-row > span {
|
||||||
|
text-align:right;
|
||||||
|
}
|
||||||
|
div.frame-row > span:nth-child(1),
|
||||||
|
div.frame-header > span:nth-child(1) {
|
||||||
|
width:30px;
|
||||||
|
}
|
||||||
|
div.frame-row > span:nth-child(2),
|
||||||
|
div.frame-header > span:nth-child(2) {
|
||||||
|
width: 67px;
|
||||||
|
}
|
||||||
|
div.frame-row > span:nth-child(3),
|
||||||
|
div.frame-header > span:nth-child(3) {
|
||||||
|
width: 77px;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
div.frame-row > span:nth-child(4),
|
||||||
|
div.frame-header > span:nth-child(4) {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
div.frame-row > span:nth-child(5),
|
||||||
|
div.frame-header > span:nth-child(5) {
|
||||||
|
width: 57px;
|
||||||
|
}
|
||||||
|
div.frame-list > div:nth-child(2n+1) {
|
||||||
|
background: beige;
|
||||||
|
}
|
||||||
|
div.frame-row {
|
||||||
|
font-size:12px;
|
||||||
|
width:100%;
|
||||||
|
background:white;
|
||||||
|
}
|
||||||
|
div.frame-pulses {
|
||||||
|
width:100%;
|
||||||
|
padding-left:7px;
|
||||||
|
padding-right:7px;
|
||||||
|
overflow:hidden;
|
||||||
|
white-space:nowrap;
|
||||||
|
text-overflow:ellipsis;
|
||||||
|
display:none;
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue