initial commit
138
arduino/CC1101.h
Normal file
@@ -0,0 +1,138 @@
|
||||
//----------------------------------------------------------------------------
|
||||
// Description: This file contains definitions specific to the CC1101.
|
||||
// The configuration registers, strobe commands, and status registers are
|
||||
// defined, as well as some common masks for these registers.
|
||||
//
|
||||
// Texas Instruments, Inc.
|
||||
// July 2006
|
||||
//----------------------------------------------------------------------------
|
||||
#ifndef CC1101_h
|
||||
#define CC1101_h
|
||||
//
|
||||
#include <inttypes.h>
|
||||
//
|
||||
// CC1101 Configuration Registers.
|
||||
//
|
||||
#define CC1101_IOCFG2 0x00 // GDO2 Output Pin Configuration
|
||||
#define CC1101_IOCFG1 0x01 // GDO1 Output Pin Configuration
|
||||
#define CC1101_IOCFG0 0x02 // GDO0 Output Pin Configuration
|
||||
#define CC1101_FIFOTHR 0x03 // RX FIFO and TX FIFO Thresholds
|
||||
#define CC1101_SYNC1 0x04 // Sync Word, High Byte
|
||||
#define CC1101_SYNC0 0x05 // Sync Word, Low Byte
|
||||
#define CC1101_PKTLEN 0x06 // Packet Length
|
||||
#define CC1101_PKTCTRL1 0x07 // Packet Automation Control
|
||||
#define CC1101_PKTCTRL0 0x08 // Packet Automation Control
|
||||
#define CC1101_ADDR 0x09 // Device Address
|
||||
#define CC1101_CHANNR 0x0A // Channel Number
|
||||
#define CC1101_FSCTRL1 0x0B // Frequency Synthesizer Control
|
||||
#define CC1101_FSCTRL0 0x0C // Frequency Synthesizer Control
|
||||
#define CC1101_FREQ2 0x0D // Frequency Control Word, High Byte
|
||||
#define CC1101_FREQ1 0x0E // Frequency Control Word, Middle Byte
|
||||
#define CC1101_FREQ0 0x0F // Frequency Control Word, Low Byte
|
||||
#define CC1101_MDMCFG4 0x10 // Modem Configuration
|
||||
#define CC1101_MDMCFG3 0x11 // Modem Configuration
|
||||
#define CC1101_MDMCFG2 0x12 // Modem Configuration
|
||||
#define CC1101_MDMCFG1 0x13 // Modem Configuration
|
||||
#define CC1101_MDMCFG0 0x14 // Modem Configuration
|
||||
#define CC1101_DEVIATN 0x15 // Modem Deviation Setting
|
||||
#define CC1101_MCSM2 0x16 // Main Radio Control State Machine Configuration
|
||||
#define CC1101_MCSM1 0x17 // Main Radio Control State Machine Configuration
|
||||
#define CC1101_MCSM0 0x18 // Main Radio Control State Machine Configuration
|
||||
#define CC1101_FOCCFG 0x19 // Frequency Offset Compensation Configuration
|
||||
#define CC1101_BSCFG 0x1A // Bit Synchronization Configuration
|
||||
#define CC1101_AGCCTRL2 0x1B // AGC Control
|
||||
#define CC1101_AGCCTRL1 0x1C // AGC Control
|
||||
#define CC1101_AGCCTRL0 0x1D // AGC Control
|
||||
#define CC1101_WOREVT1 0x1E // High Byte Event0 Timeout
|
||||
#define CC1101_WOREVT0 0x1F // Low Byte Event0 Timeout
|
||||
#define CC1101_WORCTRL 0x20 // Wake On Radio Control
|
||||
#define CC1101_FREND1 0x21 // Front End RX Configuration
|
||||
#define CC1101_FREND0 0x22 // Front End TX Configuration
|
||||
#define CC1101_FSCAL3 0x23 // Frequency Synthesizer Calibration
|
||||
#define CC1101_FSCAL2 0x24 // Frequency Synthesizer Calibration
|
||||
#define CC1101_FSCAL1 0x25 // Frequency Synthesizer Calibration
|
||||
#define CC1101_FSCAL0 0x26 // Frequency Synthesizer Calibration
|
||||
#define CC1101_RCCTRL1 0x27 // RC Oscillator Configuration
|
||||
#define CC1101_RCCTRL0 0x28 // RC Oscillator Configuration
|
||||
#define CC1101_FSTEST 0x29 // Frequency Synthesizer Calibration Control
|
||||
#define CC1101_PTEST 0x2A // Production Test
|
||||
#define CC1101_AGCTEST 0x2B // AGC Test
|
||||
#define CC1101_TEST2 0x2C // Various Test Settings
|
||||
#define CC1101_TEST1 0x2D // Various Test Settings
|
||||
#define CC1101_TEST0 0x2E // Various Test Settings
|
||||
//
|
||||
// CC1101 Status Registers.
|
||||
//
|
||||
#define CC1101_PARTNUM 0x30 // Chip ID
|
||||
#define CC1101_VERSION 0x31 // Chip ID
|
||||
#define CC1101_FREQEST 0x32 // Frequency Offset Estimate from Demodulator
|
||||
#define CC1101_LQI 0x33 // Demodulator Estimate for Link Quality
|
||||
#define CC1101_RSSI 0x34 // Received Signal Strength Indication
|
||||
#define CC1101_MARCSTATE 0x35 // Main Radio Control State Machine State
|
||||
#define CC1101_WORTIME1 0x36 // High Byte of WOR Time
|
||||
#define CC1101_WORTIME0 0x37 // Low Byte of WOR Time
|
||||
#define CC1101_PKTSTATUS 0x38 // Current GDOx Status and Packet Status
|
||||
#define CC1101_VCO_VC_DAC 0x39 // Current Setting from PLL Calibration Module
|
||||
#define CC1101_TXBYTES 0x3A // Underflow and Number of Bytes
|
||||
#define CC1101_RXBYTES 0x3B // Overflow and Number of Bytes
|
||||
#define CC1101_RCCTRL1_STATUS 0x3C // Last RC Oscillator Calibration Result
|
||||
#define CC1101_RCCTRL0_STATUS 0x3D // Last RC Oscillator Calibration Result
|
||||
//
|
||||
// CC1101 PA Table, TX FIFO and RX FIFO.
|
||||
//
|
||||
#define CC1101_PATABLE 0x3E // PA TABLE address
|
||||
#define CC1101_TXFIFO 0x3F // TX FIFO address
|
||||
#define CC1101_RXFIFO 0x3F // RX FIFO address
|
||||
//
|
||||
// CC1101 Command Strobes.
|
||||
//
|
||||
#define CC1101_SRES 0x30 // Reset CC1101 chip
|
||||
#define CC1101_SFSTXON 0x31 // Enable and calibrate frequency synthesizer (if MCSM0.FS_AUTOCAL = 1). If in RX (with CCA):
|
||||
// Go to a wait state where only the synthesizer is running (for quick RX / TX turnaround).
|
||||
#define CC1101_SXOFF 0x32 // Turn off crystal oscillator
|
||||
#define CC1101_SCAL 0x33 // Calibrate frequency synthesizer and turn it off. SCAL can be strobed from IDLE mode without
|
||||
// setting manual calibration mode (MCSM0.FS_AUTOCAL = 0)
|
||||
#define CC1101_SRX 0x34 // Enable RX. Perform calibration first if coming from IDLE and MCSM0.FS_AUTOCAL = 1
|
||||
#define CC1101_STX 0x35 // In IDLE state: Enable TX. Perform calibration first if MCSM0.FS_AUTOCAL = 1.
|
||||
// If in RX state and CCA is enabled: Only go to TX if channel is clear
|
||||
#define CC1101_SIDLE 0x36 // Exit RX / TX, turn off frequency synthesizer and exit Wake-On-Radio mode if applicable
|
||||
#define CC1101_SWOR 0x38 // Start automatic RX polling sequence (Wake-on-Radio) if WORCTRL.RC_PD = 0
|
||||
#define CC1101_SPWD 0x39 // Enter power down mode when CSn goes high
|
||||
#define CC1101_SFRX 0x3A // Flush the RX FIFO buffer. Only issue SFRX in IDLE or RXFIFO_OVERFLOW states
|
||||
#define CC1101_SFTX 0x3B // Flush the TX FIFO buffer. Only issue SFTX in IDLE or TXFIFO_UNDERFLOW states
|
||||
#define CC1101_SWORRST 0x3C // Reset real time clock to Event1 value
|
||||
#define CC1101_SNOP 0x3D // No operation. May be used to get access to the chip status byte
|
||||
//
|
||||
// CC1101 Transfer Types.
|
||||
//
|
||||
#define WRITE_BURST 0x40
|
||||
#define READ_SINGLE 0x80
|
||||
#define READ_BURST 0xC0
|
||||
//
|
||||
// CC1101 Returned Status Bytes.
|
||||
//
|
||||
#define CC1101_STATE_IDLE 0x01
|
||||
#define CC1101_STATE_ENDCAL 0x0C
|
||||
#define CC1101_STATE_RX 0x0D
|
||||
#define CC1101_STATE_RXFIFO_ERROR 0x11
|
||||
#define CC1101_STATE_TX 0x13
|
||||
#define CC1101_STATE_TX_END 0x14
|
||||
#define CC1101_STATE_TXFIFO_ERROR 0x16
|
||||
//
|
||||
// CC1101 Helper Macros.
|
||||
//
|
||||
#define setRxState() cmdStrobe(CC1101_SRX) // Enter Rx state
|
||||
#define setTxState() cmdStrobe(CC1101_STX) // Enter Tx state
|
||||
#define setIdleState() cmdStrobe(CC1101_SIDLE) // Enter Idle state
|
||||
#define flushRxFifo() cmdStrobe(CC1101_SFRX) // Flush Rx FIFO
|
||||
#define flushTxFifo() cmdStrobe(CC1101_SFTX) // Flush Tx FIFO
|
||||
//
|
||||
// CC1101 PA Table.
|
||||
//
|
||||
const uint8_t PA_TABLE[8] = {0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0};
|
||||
//
|
||||
//
|
||||
//
|
||||
#endif
|
||||
|
||||
|
||||
486
arduino/Davis.cpp
Normal file
@@ -0,0 +1,486 @@
|
||||
/*
|
||||
Davis.cpp
|
||||
|
||||
Arduino library that implements the Davis Instruments wireless
|
||||
weather station protocol for the RFBee.
|
||||
|
||||
Version 0.8
|
||||
|
||||
Copyright (c) 2012 by Ray H. Dees
|
||||
|
||||
NOTE: This code is currently below version 1.0, and therefore is
|
||||
lacking some functionality or documentation, or may not be fully
|
||||
tested.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
#include "CC1101.h"
|
||||
#include "Davis.h"
|
||||
#include "SPI.h"
|
||||
//
|
||||
// Initialize the Global Variables.
|
||||
//
|
||||
volatile uint8_t hopIndex = 0;
|
||||
volatile int pktRssi;
|
||||
volatile uint8_t pktLqi;
|
||||
volatile int freqError;
|
||||
volatile uint16_t pktCount = 0;
|
||||
volatile uint16_t pktMiss = 0;
|
||||
volatile uint8_t timer = 0;
|
||||
volatile uint8_t now = 0;
|
||||
volatile uint8_t next = 64;
|
||||
volatile uint8_t rxing = 0;
|
||||
volatile uint8_t hopping = 0;
|
||||
//
|
||||
// Intialize Class Variables.
|
||||
//
|
||||
uint8_t DAVIS::rxBuffer[BUFFER_SIZE];
|
||||
volatile uint8_t DAVIS::rxBufferIndex = 0;
|
||||
volatile uint8_t DAVIS::rxBufferLength = 0;
|
||||
volatile uint8_t DAVIS::freqComp[51];
|
||||
//
|
||||
// Implements the standard Arduino begin() function.
|
||||
//
|
||||
void DAVIS::begin(void)
|
||||
{
|
||||
NO_INTERRUPTS(); // Disable Interrupts while changes are made.
|
||||
|
||||
ADCSRA = 0x00; // Disable the analog comparator.
|
||||
|
||||
EICRA = 0x00;
|
||||
EICRA |= (1 << ISC01 | 1 << ISC00); // Setup Interrupt 0 for Rising edge.
|
||||
EIFR |= (1 << INTF0); // Clear pending interrupts.
|
||||
EIMSK |= (1 << INT0); // Enable Interrupt 0.
|
||||
|
||||
//TCCR1B = 0x00; // Disable Timer 1.
|
||||
|
||||
//ASSR = 0x00; // Set Timer 2 to System Clock.
|
||||
//TCCR2A = 0x00; // Reset TCCR2A.
|
||||
//TCCR2A = (1 << WGM21); // Set CTC mode.
|
||||
//TCCR2B = 0x00; // Reset TCCR2B.
|
||||
//TCCR2B = (1 << CS22 | 1 << CS21 | 1 << CS20); // Prescale by 1024.
|
||||
//TIFR2 = (1 << OCF2B | 1 << OCF2A | 1 << TOV2); // Clear pending interrupts.
|
||||
//OCR2A = 0x4D; // Set Compare Match for .01 seconds.
|
||||
//TIMSK2 = 0x00; // Reset TIMSK2. TIMSK2 &= ~(1 << OCIE2A) Defined as TIMER2_STOP.
|
||||
//TIMSK2 = (1 << OCIE2A); // Timer 2 Compare Match A Interrupt Enable. Defined as TIMER2_RUN.
|
||||
|
||||
pinMode(SS, OUTPUT); // Set Slave Select as an OUTPUT.
|
||||
digitalWrite(SS, HIGH); // Set it HIGH.
|
||||
pinMode(GDO0, INPUT); // Set INT0 as an INPUT.
|
||||
pinMode(GDO2, INPUT); // Set INT1 as an INPUT (Future Use).
|
||||
//
|
||||
// These pins correspond to LED's located on the Uart Bee.
|
||||
//
|
||||
pinMode(9, OUTPUT); // RSSI Pin - Indicates Hopping.
|
||||
digitalWrite(9, LOW); // Set it LOW.
|
||||
pinMode(14, OUTPUT); // ASSOC Pin - Indicates Sync.
|
||||
digitalWrite(14, LOW); // Set it LOW.
|
||||
pinMode(15, OUTPUT); // ON Pin (Not currently used.)
|
||||
digitalWrite(15, LOW); // Set it LOW.
|
||||
|
||||
SPI.begin(); // Start the SPI.
|
||||
SPCR |= (1 << SPR0);
|
||||
|
||||
reset(); // Reset the CC1101.
|
||||
setRegisters(); // Configure the CC1101.
|
||||
|
||||
for (uint8_t i = 0; i < 6; i++) // Preset the frequency compensation.
|
||||
freqComp[i] = DAVIS_FSCTRL0;
|
||||
|
||||
INTERRUPTS(); // Enable Interrupts.
|
||||
}
|
||||
//
|
||||
// Reset the CC1101.
|
||||
//
|
||||
void DAVIS::reset(void)
|
||||
{
|
||||
digitalWrite(SS, HIGH); // Deselect the CC1101.
|
||||
delayMicroseconds(10);
|
||||
digitalWrite(SS, LOW); // Select the CC1101.
|
||||
delayMicroseconds(10);
|
||||
digitalWrite(SS, HIGH); // Deselect the CC1101.
|
||||
delayMicroseconds(50);
|
||||
digitalWrite(SS, LOW); // Select the CC1101.
|
||||
SPI.transfer(CC1101_SRES); // Send reset command strobe.
|
||||
digitalWrite(SS, HIGH); // Deselect the CC1101.
|
||||
}
|
||||
//
|
||||
// Write the CC1101 Configuration Registers.
|
||||
//
|
||||
void DAVIS::setRegisters(void)
|
||||
{
|
||||
writeRegister(CC1101_IOCFG2, DAVIS_IOCFG2);
|
||||
writeRegister(CC1101_IOCFG1, DAVIS_IOCFG1);
|
||||
writeRegister(CC1101_IOCFG0, DAVIS_IOCFG0);
|
||||
writeRegister(CC1101_FIFOTHR, DAVIS_FIFOTHR);
|
||||
writeRegister(CC1101_SYNC1, DAVIS_SYNC1);
|
||||
writeRegister(CC1101_SYNC0, DAVIS_SYNC0);
|
||||
writeRegister(CC1101_PKTLEN, DAVIS_PKTLEN);
|
||||
writeRegister(CC1101_PKTCTRL1, DAVIS_PKTCTRL1);
|
||||
writeRegister(CC1101_PKTCTRL0, DAVIS_PKTCTRL0);
|
||||
writeRegister(CC1101_ADDR, DAVIS_ADDR);
|
||||
writeRegister(CC1101_CHANNR, DAVIS_CHANNR);
|
||||
writeRegister(CC1101_FSCTRL1, DAVIS_FSCTRL1);
|
||||
writeRegister(CC1101_FSCTRL0, DAVIS_FSCTRL0);
|
||||
writeRegister(CC1101_FREQ2, DAVIS_FREQ2);
|
||||
writeRegister(CC1101_FREQ1, DAVIS_FREQ1);
|
||||
writeRegister(CC1101_FREQ0, DAVIS_FREQ0);
|
||||
writeRegister(CC1101_MDMCFG4, DAVIS_MDMCFG4);
|
||||
writeRegister(CC1101_MDMCFG3, DAVIS_MDMCFG3);
|
||||
writeRegister(CC1101_MDMCFG2, DAVIS_MDMCFG2);
|
||||
writeRegister(CC1101_MDMCFG1, DAVIS_MDMCFG1);
|
||||
writeRegister(CC1101_MDMCFG0, DAVIS_MDMCFG0);
|
||||
writeRegister(CC1101_DEVIATN, DAVIS_DEVIATN);
|
||||
writeRegister(CC1101_MCSM2, DAVIS_MCSM2);
|
||||
writeRegister(CC1101_MCSM1, DAVIS_MCSM1);
|
||||
writeRegister(CC1101_MCSM0, DAVIS_MCSM0);
|
||||
writeRegister(CC1101_FOCCFG, DAVIS_FOCCFG);
|
||||
writeRegister(CC1101_BSCFG, DAVIS_BSCFG);
|
||||
writeRegister(CC1101_AGCCTRL2, DAVIS_AGCCTRL2);
|
||||
writeRegister(CC1101_AGCCTRL1, DAVIS_AGCCTRL1);
|
||||
writeRegister(CC1101_AGCCTRL0, DAVIS_AGCCTRL0);
|
||||
writeRegister(CC1101_WOREVT1, DAVIS_WOREVT1);
|
||||
writeRegister(CC1101_WOREVT0, DAVIS_WOREVT0);
|
||||
writeRegister(CC1101_WORCTRL, DAVIS_WORCTRL);
|
||||
writeRegister(CC1101_FREND1, DAVIS_FREND1);
|
||||
writeRegister(CC1101_FREND0, DAVIS_FREND0);
|
||||
writeRegister(CC1101_FSCAL3, DAVIS_FSCAL3);
|
||||
writeRegister(CC1101_FSCAL2, DAVIS_FSCAL2);
|
||||
writeRegister(CC1101_FSCAL1, DAVIS_FSCAL1);
|
||||
writeRegister(CC1101_FSCAL0, DAVIS_FSCAL0);
|
||||
writeRegister(CC1101_RCCTRL1, DAVIS_RCCTRL1);
|
||||
writeRegister(CC1101_RCCTRL0, DAVIS_RCCTRL0);
|
||||
writeRegister(CC1101_FSTEST, DAVIS_FSTEST);
|
||||
writeRegister(CC1101_PTEST, DAVIS_PTEST);
|
||||
writeRegister(CC1101_AGCTEST, DAVIS_AGCTEST);
|
||||
writeRegister(CC1101_TEST2, DAVIS_TEST2);
|
||||
writeRegister(CC1101_TEST1, DAVIS_TEST1);
|
||||
writeRegister(CC1101_TEST0, DAVIS_TEST0);
|
||||
writeBurst(CC1101_PATABLE, (uint8_t*)PA_TABLE, 8);
|
||||
setFrequency(hopIndex);
|
||||
digitalWrite(15, HIGH);
|
||||
}
|
||||
//
|
||||
// Write a strobe command.
|
||||
//
|
||||
void DAVIS::cmdStrobe(uint8_t command)
|
||||
{
|
||||
digitalWrite(SS, LOW); // Select the CC1101.
|
||||
SPI.transfer(command); // Send strobe command.
|
||||
digitalWrite(SS, HIGH); // Deselect the CC1101 .
|
||||
}
|
||||
//
|
||||
// Write to a single register.
|
||||
//
|
||||
void DAVIS::writeRegister(uint8_t regAddr, uint8_t value)
|
||||
{
|
||||
digitalWrite(SS, LOW); // Select the CC1101.
|
||||
SPI.transfer(regAddr); // Send register address.
|
||||
SPI.transfer(value); // Send value.
|
||||
digitalWrite(SS, HIGH); // Deselect the CC1101.
|
||||
}
|
||||
//
|
||||
// Write to sequential registers.
|
||||
//
|
||||
void DAVIS::writeBurst(uint8_t regAddr, uint8_t *buffer, uint8_t length)
|
||||
{
|
||||
uint8_t addr, i;
|
||||
|
||||
addr = regAddr | WRITE_BURST; // Enable burst transfer.
|
||||
digitalWrite(SS, LOW); // Select the CC1101.
|
||||
SPI.transfer(addr); // Send register base address.
|
||||
for(i = 0; i < length; i++)
|
||||
SPI.transfer(buffer[i]); // Send values byte by byte.
|
||||
digitalWrite(SS, HIGH); // Deselect the CC1101.
|
||||
}
|
||||
//
|
||||
// Read from a single register.
|
||||
//
|
||||
uint8_t DAVIS::readRegister(uint8_t regAddr)
|
||||
{
|
||||
uint8_t addr, value;
|
||||
|
||||
addr = regAddr | READ_SINGLE;
|
||||
digitalWrite(SS, LOW); // Select the CC1101.
|
||||
SPI.transfer(addr); // Send register address.
|
||||
value = SPI.transfer(0x00); // Read value.
|
||||
digitalWrite(SS, HIGH); // Deselect the CC1101.
|
||||
|
||||
return value;
|
||||
}
|
||||
//
|
||||
// Read from sequential registers.
|
||||
//
|
||||
void DAVIS::readBurst(uint8_t regAddr, uint8_t *buffer, uint8_t length)
|
||||
{
|
||||
uint8_t addr, i;
|
||||
|
||||
addr = regAddr | READ_BURST;
|
||||
digitalWrite(SS, LOW); // Select the CC1101.
|
||||
SPI.transfer(addr); // Send register base address.
|
||||
for(i = 0; i < length; i++)
|
||||
buffer[i] = SPI.transfer(0x00); // Read registers byte by byte.
|
||||
digitalWrite(SS, HIGH); // Deselect the CC1101.
|
||||
}
|
||||
//
|
||||
// Read the status from a register.
|
||||
//
|
||||
uint8_t DAVIS::readStatus(uint8_t regAddr)
|
||||
{
|
||||
uint8_t addr, value;
|
||||
|
||||
addr = regAddr | READ_BURST;
|
||||
digitalWrite(SS, LOW); // Select the CC1101.
|
||||
SPI.transfer(addr); // Send register address.
|
||||
value = SPI.transfer(0x00); // Read status.
|
||||
digitalWrite(SS, HIGH); // Deselect the CC1101.
|
||||
|
||||
return value;
|
||||
}
|
||||
//
|
||||
// Sets Idle Mode.
|
||||
//
|
||||
void DAVIS::idle(void)
|
||||
{
|
||||
setIdleState();
|
||||
}
|
||||
//
|
||||
// Sets Rx Mode.
|
||||
//
|
||||
void DAVIS::rx(void)
|
||||
{
|
||||
setRxState();
|
||||
|
||||
while ((Radio.readStatus(CC1101_MARCSTATE) & 0x1F) != CC1101_STATE_RX)
|
||||
delayMicroseconds(900);
|
||||
|
||||
rxing = 1;
|
||||
|
||||
writeRegister(CC1101_IOCFG0, DAVIS_IOCFG0);
|
||||
}
|
||||
//
|
||||
// Controls frequency hopping.
|
||||
//
|
||||
void DAVIS::hop(void)
|
||||
{
|
||||
digitalWrite(9, HIGH); // Turn on hopping LED.
|
||||
|
||||
hopIndex++; // Increment the index
|
||||
|
||||
if (hopIndex > 4) // Clamp at 51.
|
||||
hopIndex = 0;
|
||||
|
||||
writeRegister(CC1101_FSCTRL0, freqComp[hopIndex]); // Write FSCTRL0.
|
||||
|
||||
setFrequency(hopIndex); // Set the frequency.
|
||||
|
||||
digitalWrite(9, LOW); // Turn off hopping LED.
|
||||
}
|
||||
//
|
||||
// Sets the RX or TX frequency from the lookup table.
|
||||
//
|
||||
void DAVIS::setFrequency(uint8_t index)
|
||||
{
|
||||
idle(); // Make sure we are in idle state.
|
||||
|
||||
writeRegister(CC1101_FREQ2, pgm_read_byte(&FREQ_2[index])); // Write FREQ2.
|
||||
writeRegister(CC1101_FREQ1, pgm_read_byte(&FREQ_1[index])); // Write FREQ1.
|
||||
writeRegister(CC1101_FREQ0, pgm_read_byte(&FREQ_0[index])); // Write FREQ0.
|
||||
|
||||
flush(); // Flush everything.
|
||||
}
|
||||
//
|
||||
// Receive the data packet.
|
||||
//
|
||||
void DAVIS::rxData(void)
|
||||
{
|
||||
uint8_t pktVerify, pktLength, freqEst, addr, i;
|
||||
uint16_t pktCrc = 0xFFFF;
|
||||
//////////////////////////////////////////// See Errata Note for this section.
|
||||
pktVerify = readStatus(CC1101_RXBYTES) & 0x7F;
|
||||
|
||||
do
|
||||
{
|
||||
pktLength = pktVerify;
|
||||
pktVerify = readStatus(CC1101_RXBYTES) & 0x7F;
|
||||
}
|
||||
|
||||
while (pktLength != pktVerify);
|
||||
////////////////////////////////////////////
|
||||
|
||||
if ((readStatus(CC1101_MARCSTATE) & 0x1F) == CC1101_STATE_RXFIFO_ERROR) // Check for Rx FIFO Overrun.
|
||||
pktLength = 0;
|
||||
|
||||
|
||||
if (pktLength == DAVIS_PACKET_LENGTH)
|
||||
{
|
||||
addr = CC1101_RXFIFO | READ_BURST;
|
||||
|
||||
digitalWrite(SS, LOW); // Select the CC1101.
|
||||
|
||||
SPI.transfer(addr); // Send register base address.
|
||||
|
||||
SPI.setBitOrder(LSBFIRST); // Reverse the bit order.
|
||||
|
||||
for(i = 0; i < 10; i++)
|
||||
rxBuffer[i] = SPI.transfer(0x00); // Read rx data into the buffer.
|
||||
|
||||
SPI.setBitOrder(MSBFIRST); // Set the bit order back.
|
||||
|
||||
for(i = 10; i < 12; i++)
|
||||
rxBuffer[i] = SPI.transfer(0x00); // Read RSSI & LQI into the buffer.
|
||||
|
||||
digitalWrite(SS, HIGH); // Deselect the CC1101.
|
||||
|
||||
pktCrc = calcCrc(rxBuffer, 8); // Get the CRC for first eight bytes.
|
||||
|
||||
//pktCrc = 0x0000; // Uncomment to disable CRC checking.
|
||||
|
||||
if (pktCrc == 0x0000) // If CRC = 0, valid data is now available.
|
||||
{
|
||||
now = timer = 0;
|
||||
pktRssi = calcRssi(rxBuffer[10]);
|
||||
pktLqi = (rxBuffer[11] & 0x7F);
|
||||
freqEst = readStatus(CC1101_FREQEST);
|
||||
freqError = calcFreqError(freqEst);
|
||||
freqComp[hopIndex] = freqComp[hopIndex] + freqEst;
|
||||
pktCount++;
|
||||
rxBufferLength = pktLength;
|
||||
rxing = 0;
|
||||
writeRegister(CC1101_IOCFG0, DAVIS_DISABLED); // Disable CC1101 Rx interrupts.
|
||||
digitalWrite(14, HIGH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
idle();
|
||||
flushRxFifo();
|
||||
rx();
|
||||
}
|
||||
//
|
||||
// Implements the standard Arduino available() function.
|
||||
//
|
||||
int DAVIS::available(void)
|
||||
{
|
||||
if (rxBufferIndex == rxBufferLength)
|
||||
return -1;
|
||||
|
||||
else
|
||||
return rxBufferLength - rxBufferIndex;
|
||||
}
|
||||
//
|
||||
// Implements the standard Arduino read() function.
|
||||
//
|
||||
uint8_t DAVIS::read(void)
|
||||
{
|
||||
uint8_t value = 0x00;
|
||||
|
||||
if (rxBufferIndex < rxBufferLength)
|
||||
{
|
||||
value = rxBuffer[rxBufferIndex];
|
||||
rxBufferIndex++;
|
||||
}
|
||||
|
||||
else
|
||||
rxBufferIndex = rxBufferLength = 0;
|
||||
|
||||
return value;
|
||||
}
|
||||
//
|
||||
// Flushes FIFO's and the receive buffer.
|
||||
//
|
||||
void DAVIS::flush(void)
|
||||
{
|
||||
flushRxFifo(); // Flush both CC1101 FIFO's.
|
||||
flushTxFifo();
|
||||
|
||||
for (int i = 0; i < BUFFER_SIZE; i++) // Flush the rxBuffer.
|
||||
rxBuffer[i] = 0x00;
|
||||
|
||||
rxBufferLength = rxBufferIndex = 0;
|
||||
}
|
||||
//
|
||||
// Calculates the 16 bit CRC using the Davis method.
|
||||
//
|
||||
uint16_t DAVIS::calcCrc(uint8_t *buffer, uint8_t length)
|
||||
{
|
||||
uint16_t crc = 0x0000;
|
||||
|
||||
while (length-- > 0)
|
||||
{
|
||||
crc = (crc << 8) ^ pgm_read_word(&CRC_TABLE[(crc >> 8) ^ (*buffer++)]);
|
||||
};
|
||||
|
||||
return crc;
|
||||
}
|
||||
//
|
||||
// Calculates the RSSI in dBm.
|
||||
//
|
||||
char DAVIS::calcRssi(uint8_t value)
|
||||
{
|
||||
int rssi;
|
||||
|
||||
if (value >= 128)
|
||||
rssi = ((value - 256) >> 1) - 74; // CC1101 RSSI offset value.
|
||||
|
||||
else
|
||||
rssi = (value >> 1) - 74;
|
||||
|
||||
if (rssi < -128)
|
||||
rssi = -128;
|
||||
|
||||
return rssi;
|
||||
}
|
||||
//
|
||||
// Calculates the Frequency Error for display purposes.
|
||||
//
|
||||
int DAVIS::calcFreqError(uint8_t value)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (value >= 128)
|
||||
error = ((value - 256) >> 1);
|
||||
|
||||
else
|
||||
error = (value >> 1);
|
||||
|
||||
return error;
|
||||
}
|
||||
//
|
||||
// INT0 Interrupt Service Routine.
|
||||
//
|
||||
ISR(INT0_vect)
|
||||
{
|
||||
Radio.rxData();
|
||||
}
|
||||
//
|
||||
// Timer 2 Compare Match A Interrupt Service Routine.
|
||||
//
|
||||
ISR(TIMER2_COMPA_vect)
|
||||
{
|
||||
timer++;
|
||||
if (timer >= 4)
|
||||
{
|
||||
timer = 0;
|
||||
now++;
|
||||
next++;
|
||||
}
|
||||
if (now >= 44 && rxing == 0)
|
||||
Radio.rx();
|
||||
}
|
||||
//
|
||||
//
|
||||
//
|
||||
DAVIS Radio;
|
||||
239
arduino/Davis.h
Normal file
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
Davis.h
|
||||
|
||||
Arduino library that implements the Davis Instruments Vantage Vue
|
||||
weather station protocol for the RFBee.
|
||||
|
||||
Version 0.8
|
||||
|
||||
Copyright (c) 2012 by Ray H. Dees
|
||||
|
||||
NOTE: This code is currently below version 1.0, and therefore is
|
||||
lacking some functionality or documentation, or may not be fully
|
||||
tested.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
//
|
||||
#ifndef Davis_h
|
||||
#define Davis_h
|
||||
//
|
||||
#include "Arduino.h"
|
||||
//
|
||||
// Arduino Definitions.
|
||||
//
|
||||
#define SS 10 // Arduino Digital Pin used for Slave Select - Out
|
||||
#define GDO0 2 // Arduino Digital Pin used for INT0 - In
|
||||
#define GDO2 3 // Arduino Digital Pin used for INT1 - In
|
||||
//
|
||||
#ifndef NO_INTERRUPTS()
|
||||
#define NO_INTERRUPTS() uint8_t sreg = SREG; cli()
|
||||
#define INTERRUPTS() SREG = sreg
|
||||
#endif
|
||||
//
|
||||
#ifndef TIMER2_RUN()
|
||||
#define TIMER2_RUN() TCNT2 = 0x00; TIMSK2 = (1 << OCIE2A)
|
||||
#define TIMER2_RESET() TCNT2 = 0x00; timer = 0
|
||||
#define TIMER2_STOP() TIMSK2 &= ~(1 << OCIE2A)
|
||||
#endif
|
||||
//
|
||||
#define BUFFER_SIZE 16 // TX & RX Buffer Size
|
||||
//
|
||||
// Davis Packet Length.
|
||||
//
|
||||
#define DAVIS_PACKET_LENGTH 12 // The actual packet length including RSSI & LQI
|
||||
//
|
||||
// Davis Register Configuration Settings.
|
||||
//
|
||||
#define DAVIS_IOCFG2 0x2E // GDO2 Output Pin Configuration
|
||||
#define DAVIS_IOCFG1 0x2E // GDO1 Output Pin Configuration
|
||||
#define DAVIS_IOCFG0 0x01 // GDO0 Output Pin Configuration
|
||||
//
|
||||
#define DAVIS_FIFOTHR 0x42 // RX FIFO and TX FIFO Thresholds
|
||||
//
|
||||
#define DAVIS_SYNC1 0xCB // Synchronization word, high byte
|
||||
#define DAVIS_SYNC0 0x89 // Synchronization word, low byte
|
||||
//
|
||||
#define DAVIS_PKTLEN 0x0A // Packet Length
|
||||
#define DAVIS_PKTCTRL1 0xC4 // Packet Automation Control
|
||||
#define DAVIS_PKTCTRL0 0x00 // Packet Automation Control
|
||||
//
|
||||
#define DAVIS_ADDR 0x00 // Device Address
|
||||
#define DAVIS_CHANNR 0x00 // Channel Number
|
||||
#define DAVIS_FSCTRL1 0x06 // Frequency Synthesizer Control
|
||||
#define DAVIS_FSCTRL0 0xF0 // Frequency Synthesizer Control
|
||||
//
|
||||
#define DAVIS_FREQ2 0x23 // Frequency Control Word, High Byte
|
||||
#define DAVIS_FREQ1 0x0D // Frequency Control Word, Middle Byte
|
||||
#define DAVIS_FREQ0 0x97 // Frequency Control Word, Low Byte
|
||||
//
|
||||
#define DAVIS_MDMCFG4 0xC9 // Modem Configuration
|
||||
#define DAVIS_MDMCFG3 0x83 // Modem Configuration
|
||||
#define DAVIS_MDMCFG2 0x12 // Modem Configuration
|
||||
#define DAVIS_MDMCFG1 0x21 // Modem Configuration
|
||||
#define DAVIS_MDMCFG0 0xF9 // Modem Configuration
|
||||
//
|
||||
#define DAVIS_DEVIATN 0x24 // Modem Deviation Setting
|
||||
//
|
||||
#define DAVIS_MCSM2 0x07 // Main Radio Control State Machine Configuration
|
||||
#define DAVIS_MCSM1 0x00 // Main Radio Control State Machine Configuration
|
||||
#define DAVIS_MCSM0 0x18 // Main Radio Control State Machine Configuration
|
||||
//
|
||||
#define DAVIS_FOCCFG 0x16 // Frequency Offset Compensation Configuration
|
||||
#define DAVIS_BSCFG 0x6C // Bit Synchronization Configuration
|
||||
//
|
||||
#define DAVIS_AGCCTRL2 0x43 // AGC Control
|
||||
#define DAVIS_AGCCTRL1 0x40 // AGC Control
|
||||
#define DAVIS_AGCCTRL0 0x91 // AGC Control
|
||||
//
|
||||
#define DAVIS_WOREVT1 0x87 // High Byte Event0 Timeout
|
||||
#define DAVIS_WOREVT0 0x6B // Low Byte Event0 Timeout
|
||||
#define DAVIS_WORCTRL 0xF8 // Wake On Radio Control
|
||||
//
|
||||
#define DAVIS_FREND1 0x56 // Front End RX Configuration
|
||||
#define DAVIS_FREND0 0x10 // Front End TX Configuration
|
||||
//
|
||||
#define DAVIS_FSCAL3 0xEF // Frequency Synthesizer Calibration
|
||||
#define DAVIS_FSCAL2 0x2B // Frequency Synthesizer Calibration
|
||||
#define DAVIS_FSCAL1 0x2F // Frequency Synthesizer Calibration
|
||||
#define DAVIS_FSCAL0 0x1F // Frequency Synthesizer Calibration
|
||||
//
|
||||
#define DAVIS_RCCTRL1 0x00 // RC Oscillator Configuration
|
||||
#define DAVIS_RCCTRL0 0x00 // RC Oscillator Configuration
|
||||
//
|
||||
#define DAVIS_FSTEST 0x59 // Frequency Synthesizer Calibration Control
|
||||
#define DAVIS_PTEST 0x7F // Production Test
|
||||
#define DAVIS_AGCTEST 0x3F // AGC Test
|
||||
//
|
||||
#define DAVIS_TEST2 0x81 // Various Test Settings
|
||||
#define DAVIS_TEST1 0x35 // Various Test Settings
|
||||
#define DAVIS_TEST0 0x09 // Various Test Settings
|
||||
//
|
||||
#define DAVIS_DISABLED 0x2E // Disables GPIO's
|
||||
//
|
||||
// Table for FREQ2 settings. (Vantage VUE - EU 868 mHz)
|
||||
//
|
||||
static const uint8_t __attribute__ ((progmem)) FREQ_2[5] =
|
||||
{
|
||||
0x21, 0x21, 0x21, 0x21, 0x21
|
||||
};
|
||||
//
|
||||
// Table for FREQ1 settings.
|
||||
//
|
||||
static const uint8_t __attribute__ ((progmem)) FREQ_1[5] =
|
||||
{
|
||||
0x62, 0x65, 0x67, 0x64, 0x66
|
||||
};
|
||||
//
|
||||
// Table for FREQ0 settings.
|
||||
//
|
||||
static const uint8_t __attribute__ ((progmem)) FREQ_0[5] =
|
||||
{
|
||||
0xE2, 0x40, 0x9D, 0x11, 0x6F
|
||||
};
|
||||
//
|
||||
// CRC Lookup Table.
|
||||
//
|
||||
static const uint16_t __attribute__ ((progmem)) CRC_TABLE[256] =
|
||||
{
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
|
||||
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
|
||||
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
|
||||
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
|
||||
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
|
||||
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
|
||||
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
|
||||
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
|
||||
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
|
||||
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
|
||||
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
|
||||
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
|
||||
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
|
||||
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
|
||||
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
|
||||
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
|
||||
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
|
||||
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
|
||||
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
|
||||
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
|
||||
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
|
||||
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
||||
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
|
||||
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
|
||||
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
|
||||
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
|
||||
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
|
||||
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
|
||||
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
|
||||
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
|
||||
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
|
||||
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
|
||||
};
|
||||
//
|
||||
// Global Variables.
|
||||
//
|
||||
extern volatile uint8_t hopIndex;
|
||||
extern volatile int pktRssi;
|
||||
extern volatile uint8_t pktLqi;
|
||||
extern volatile int freqError;
|
||||
extern volatile uint16_t pktCount;
|
||||
extern volatile uint16_t pktMiss;
|
||||
extern volatile uint8_t timer;
|
||||
extern volatile uint8_t now;
|
||||
extern volatile uint8_t next;
|
||||
extern volatile uint8_t rxing;
|
||||
extern volatile uint8_t hopping;
|
||||
//
|
||||
// DAVIS Class.
|
||||
//
|
||||
class DAVIS
|
||||
{
|
||||
private:
|
||||
|
||||
static uint8_t rxBuffer[];
|
||||
static volatile uint8_t rxBufferIndex;
|
||||
static volatile uint8_t rxBufferLength;
|
||||
static volatile uint8_t freqComp[];
|
||||
|
||||
public:
|
||||
|
||||
void begin(void);
|
||||
void reset(void);
|
||||
void setRegisters(void);
|
||||
void cmdStrobe(uint8_t command);
|
||||
void writeRegister(uint8_t regAddr, uint8_t value);
|
||||
void writeBurst(uint8_t regAddr, uint8_t *buffer, uint8_t length);
|
||||
uint8_t readRegister(uint8_t regAddr);
|
||||
void readBurst(uint8_t regAddr, uint8_t *buffer, uint8_t length);
|
||||
uint8_t readStatus(uint8_t regAddr);
|
||||
void idle(void);
|
||||
void rx(void);
|
||||
void hop(void);
|
||||
void setFrequency(uint8_t index);
|
||||
void rxData(void);
|
||||
int available(void);
|
||||
uint8_t read(void);
|
||||
void flush(void);
|
||||
uint16_t calcCrc(uint8_t *buffer, uint8_t length);
|
||||
char calcRssi(uint8_t value);
|
||||
int calcFreqError(uint8_t value);
|
||||
};
|
||||
|
||||
extern DAVIS Radio;
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
#endif
|
||||
101
arduino/davis_receiver.ino
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
/*
|
||||
Davis logger with Wifi esp8266 as bridge.
|
||||
*/
|
||||
|
||||
// připojení potřebných knihoven
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_Sensor.h>
|
||||
#include <Adafruit_BMP280.h>
|
||||
#include <SparkFunHTU21D.h>
|
||||
HTU21D sensorHTU;
|
||||
|
||||
#define BMP280_ADDRESS (0x76)
|
||||
Adafruit_BMP280 bmp;
|
||||
int height = 450;
|
||||
|
||||
#include "Davis.h"
|
||||
#include "SPI.h"
|
||||
//
|
||||
byte temp;
|
||||
String data;
|
||||
int incByte = 0;
|
||||
|
||||
void setup()
|
||||
{
|
||||
delay(100);
|
||||
Serial.begin(9600);
|
||||
delay(100);
|
||||
Radio.begin();
|
||||
Radio.rx();
|
||||
if (!bmp.begin(BMP280_ADDRESS)) {
|
||||
Serial.println("BMP280 sensor not found, check wires!");
|
||||
while (1);
|
||||
}
|
||||
sensorHTU.begin();
|
||||
Serial.println("Done Setup");
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (Radio.available() > 0)
|
||||
printPacket();
|
||||
if (hopping)
|
||||
{
|
||||
hopping = 0;
|
||||
Radio.hop();
|
||||
Radio.rx(); // Un-comment if not using Timer 2.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void printPacket(void)
|
||||
{
|
||||
byte header = Radio.read(); // Where's the header?
|
||||
byte package[16];
|
||||
|
||||
package[0] = hopIndex; // Which frequency are we at?
|
||||
package[1] = header;
|
||||
for (int i = 2; i < 11; i++) // The received packet.
|
||||
{
|
||||
package[i] = Radio.read();
|
||||
}
|
||||
package[11] = pktRssi;
|
||||
package[12] = pktLqi;
|
||||
package[13] = freqError;
|
||||
package[14] = next;
|
||||
package[15] = pktCount;
|
||||
float tempBMP = bmp.readTemperature();
|
||||
float pressBMP = (bmp.readPressure()/100.00);
|
||||
float sea_level = pressBMP * pow((1 - (0.0065 * height) / (tempBMP + 0.0065 * height + 273.15)), -5.257);
|
||||
delay(100);
|
||||
float htu_tmp = sensorHTU.readTemperature();
|
||||
float htu_humi = sensorHTU.readHumidity();
|
||||
if (htu_tmp > 125 | htu_humi > 100) {
|
||||
Serial.println("Sensor HTU21D communication error!");
|
||||
}
|
||||
String data = "{'hop':" + String(package[0]) + "," +
|
||||
"'h':" + String(package[1]) + "," +
|
||||
"'b0':" + String(package[2]) + "," +
|
||||
"'b1':" + String(package[3]) + "," +
|
||||
"'b2':" + String(package[4]) + "," +
|
||||
"'b3':" + String(package[5]) + "," +
|
||||
"'b4':" + String(package[6]) + "," +
|
||||
"'b5':" + String(package[7]) + "," +
|
||||
"'b6':" + String(package[8]) + "," +
|
||||
"'b7':" + String(package[9]) + "," +
|
||||
"'b8':" + String(package[10]) + "," +
|
||||
"'b9':" + String(package[11]) + "," +
|
||||
"'Ti':" + String(tempBMP) + "," +
|
||||
"'P':" + String(pressBMP) + "," +
|
||||
"'P0':" + String(sea_level) + "," +
|
||||
"'Thtu':" + String(htu_tmp) + "," +
|
||||
"'Hhtu':" + String(htu_humi) + "," +
|
||||
"'rssi':" + String(package[12]) + "," +
|
||||
"'lqi':" + String(package[13]) + "," +
|
||||
"'nxt':" + String(package[14]) + "," +
|
||||
"'cnt':" + String(package[15]) + "}";
|
||||
Serial.println(data);
|
||||
hopping = 1; // Turn on hopping.
|
||||
|
||||
}
|
||||
1
arduino/readme.txt
Normal file
@@ -0,0 +1 @@
|
||||
Working version, flawlesly!
|
||||
543
python/davis_etl.py
Executable file
@@ -0,0 +1,543 @@
|
||||
#!/usr/bin/python -u
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Type: Python 3.x script
|
||||
Author: Milan Toman (milan.v.toman@gmail.com)
|
||||
Description: Weather station (davis vantage vue) collector, coupled with
|
||||
the infamous arduino source on
|
||||
http://wp.spoton.cz/2017/11/24/davis-vantague-vue-arduino-and-a-raspberry-pi-3/
|
||||
en-fucking-joy
|
||||
|
||||
TODO: Rainrate going bonkers, check that shite.
|
||||
|
||||
|
||||
influxDB SCHEMA:
|
||||
|
||||
DB weather
|
||||
measure wind
|
||||
----------------
|
||||
value | speed or direction or windgust
|
||||
---------------------------------------
|
||||
field tag
|
||||
|
||||
measure temphumi
|
||||
----------------
|
||||
temperature | humidity | external, internal | pressure
|
||||
---------------------------------------------------------
|
||||
field field tag field
|
||||
|
||||
measure rain
|
||||
----------------
|
||||
rain | rate / total / intensity | restart if zero, was it 65535 => before?
|
||||
---------------------------------------------
|
||||
field tag field(int)
|
||||
|
||||
|
||||
|
||||
DB status
|
||||
|
||||
ISS measure
|
||||
----------------
|
||||
voltage | solar or capacitor | state / lqi / | battery or future_shit |
|
||||
----------------------------------------------------------------
|
||||
field tag field tag
|
||||
|
||||
RasPI system
|
||||
----------------
|
||||
usage | disk, mem, cpu, eth, wifi %
|
||||
------------------------------------
|
||||
field | tag
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Import libraries
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
# mandatory
|
||||
import requests
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
import argparse
|
||||
import time
|
||||
import datetime
|
||||
import serial
|
||||
import simplejson as json
|
||||
import influxdb
|
||||
|
||||
# optionally, future modules, locally available, I hate dependencies
|
||||
from pprint import pprint
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
sys.path.append(_SCRIPT_PATH + "/home/pi/test/lib")
|
||||
#print(_SCRIPT_PATH + "/lib")
|
||||
|
||||
#import ventilLogger
|
||||
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Define variables
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
_VERSION = 2.0
|
||||
_NAME = u"Vantage Vue Decoding shite"
|
||||
_LOG_DIR = _SCRIPT_PATH + '/log/'
|
||||
_LOG_FILE_ROOT = re.sub(u'./', '', sys.argv[0])
|
||||
_LOG_FILE = _LOG_DIR + _LOG_FILE_ROOT + u'.log'
|
||||
_DEBUG_FILE = _LOG_DIR + _LOG_FILE_ROOT + u'.dbg'
|
||||
# finite loop implementation, tout for 43200 cycles
|
||||
tout = 0
|
||||
|
||||
_ABS_ZERO = 273.15
|
||||
_HEIGHT = 455
|
||||
temp = {}
|
||||
wind = {}
|
||||
humidity = 0
|
||||
pressure_adjusted = 0
|
||||
supercap = 0
|
||||
solarvolt = 0
|
||||
rainstate = 0
|
||||
rainrate = 0
|
||||
rain = 0
|
||||
influx_status_write = []
|
||||
influx_weather_write = []
|
||||
|
||||
influx_host = 'localhost'
|
||||
influx_port = 8086
|
||||
influx_user = 'pi'
|
||||
influx_pwd = 'freedavis'
|
||||
weather_db = 'weather_v2'
|
||||
status_db = 'status'
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Set up logging - disabled, need to enable this iin future
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Setup arguments and Options - not edited, sample shite
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
desc = u'''\
|
||||
DESCRIPTION:
|
||||
Vantage Vue wireless data transfer decoder, V2
|
||||
consult http://wp.spoton.cz/2017/11/24/davis-vantague-vue-arduino-and-a-raspberry-pi-3/
|
||||
for wtf is going on
|
||||
'''
|
||||
epi = u'''\
|
||||
ERROR CODES:
|
||||
?
|
||||
|
||||
EXAMPLES:
|
||||
?
|
||||
|
||||
'''
|
||||
formatter = argparse.RawDescriptionHelpFormatter
|
||||
arg_parser = argparse.ArgumentParser(description = desc,
|
||||
formatter_class = formatter,
|
||||
epilog = textwrap.dedent(epi))
|
||||
|
||||
arg_parser.add_argument('-d', '--details',
|
||||
help = 'help',
|
||||
action='store_true')
|
||||
arg_parser.add_argument('-v', '--verbose',
|
||||
help = 'help',
|
||||
action='store_true')
|
||||
arg_parser.add_argument('-p', '--section',
|
||||
dest = 'section',
|
||||
default = ['last', 'count', 'diff'],
|
||||
choices = ['last', 'count', 'diff'],
|
||||
nargs = '+',
|
||||
type = str,
|
||||
help = 'help')
|
||||
arg_parser.add_argument('-s', '--snapusage',
|
||||
help = 'help',
|
||||
action='store_true')
|
||||
|
||||
args = arg_parser.parse_args()
|
||||
if args.details:
|
||||
_details = True
|
||||
else:
|
||||
_details = False
|
||||
if args.verbose:
|
||||
_more_details = True
|
||||
else:
|
||||
_more_details = False
|
||||
if args.snapusage:
|
||||
_SNAP_USAGE = True
|
||||
else:
|
||||
_SNAP_USAGE = False
|
||||
try:
|
||||
_sections = args.sections
|
||||
except:
|
||||
_sections = ['last', 'count', 'diff']
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Generic, standalone functions
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
|
||||
# Obvious shit, set up the client class
|
||||
influx_weather_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, weather_db
|
||||
)
|
||||
influx_status_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, status_db
|
||||
)
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Classes
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
|
||||
class davisDecoder(object):
|
||||
def __init__(self):
|
||||
__name__ = u'Davis value decoder class'
|
||||
self.height = _HEIGHT
|
||||
self.temp_dict = {}
|
||||
|
||||
def load_external_data(self):
|
||||
# Data external to the ISS
|
||||
self.pressure = float(davis_data['P'])
|
||||
self.inside_temp = round((float(davis_data['Ti'])\
|
||||
+ float(davis_data['Thtu'])) / 2, 2)
|
||||
self.inside_hum = davis_data['Hhtu']
|
||||
|
||||
def zero_fill(self, data):
|
||||
binary_data = format(int(data), '08b')
|
||||
msb = binary_data[0:4]
|
||||
lsb = binary_data[4:]
|
||||
result = {"MSB": msb, "LSB": lsb}
|
||||
return result
|
||||
|
||||
def davis_id(self, header):
|
||||
bin_header = self.zero_fill(header)
|
||||
davis_id = hex(int(bin_header['LSB'][1:], 2))
|
||||
raw_id = bin_header['MSB']
|
||||
battery_low = bin_header['LSB'][0]
|
||||
davis_packet_id = hex(int(raw_id, 2))
|
||||
result = {"davis_id": davis_id,
|
||||
"packet_id": davis_packet_id,
|
||||
"bat_low": battery_low}
|
||||
return result
|
||||
|
||||
def decode_wind(self, databytes):
|
||||
# wind speed in mph, i suppose. Let's convert it
|
||||
wind_speed = round(float(databytes['windspeed'] * 1.60934), 1)
|
||||
wind_direction_factor = round(float(360)/float(255), 1)
|
||||
wind_direction = databytes['winddir']
|
||||
wind_direction = float(wind_direction) * wind_direction_factor
|
||||
result = {"speed": wind_speed, "direction": wind_direction}
|
||||
return result
|
||||
|
||||
def decode_temp(self, temp):
|
||||
temp_f = (float(temp)) / float(160) # in Fahrenheit
|
||||
temp_c = round((temp_f - 32) * float(5)/float(9), 1)
|
||||
result = {"celsius": temp_c, "fahrenheit": temp_f}
|
||||
return result
|
||||
|
||||
def decode_humidity(self, hum):
|
||||
pass
|
||||
|
||||
def adjust_pressure(self, temp):
|
||||
sh = 0.0065 * self.height
|
||||
base = 1 - (sh) / (temp + sh + _ABS_ZERO)
|
||||
result = round(self.pressure * pow(base, -5.257), 2)
|
||||
return result
|
||||
|
||||
def supercap_decode(self, byte2, byte3):
|
||||
cap = (byte2 << 2) + (byte3 >> 6)
|
||||
result = float(cap / 100.00)
|
||||
return result
|
||||
|
||||
def solarvolt_decode(self, byte2, byte3):
|
||||
solar = (byte2 << 1) + (byte3 >> 7)
|
||||
result = float(solar)
|
||||
return result
|
||||
|
||||
def rain_decode(self, rain):
|
||||
result = float(rain & 0x7F)
|
||||
return result
|
||||
|
||||
def rainrate_decode(self, byte2, byte3):
|
||||
# if byte3(b2 here) is 0xFF, or 255, there is not rain
|
||||
if byte2 == 255:
|
||||
rainstate = 0
|
||||
else:
|
||||
# light rain (rate in mm/h, time/clicks in seconds)
|
||||
if (byte3 & 0x40) == 0:
|
||||
rainstate = 1
|
||||
time_between_clicks = ((byte3 & 0x30) / 16 * 250) + byte2
|
||||
rainrate = 720 / (((byte3 & 0x30) / 16 * 250) + byte2)
|
||||
# strong rain (rate in mm/h, time/clicks in seconds)
|
||||
elif (byte3 & 0x40) == 0x40:
|
||||
rainstate = 2
|
||||
time_between_clicks = (((byte3 & 0x30) / 16 * 250) + byte2) / 16
|
||||
rainrate = 11520 / (((byte3 & 0x30) / 16 * 250) + byte2)
|
||||
result = {"state": float(rainstate), "rate": float(rainrate)}
|
||||
return result
|
||||
|
||||
class DBwriter(object):
|
||||
def __init__(self):
|
||||
__name__ = "Database writer class, Influx"
|
||||
|
||||
def construct(self, connector, measurement, fields, tags):
|
||||
""" Takes values in a writes them to influxdb
|
||||
|
||||
requires: list(connector): connector with all ticks to be written
|
||||
at once
|
||||
str(measurement): the measurement ID to be written
|
||||
dict(fields): fields to be written in one tick
|
||||
dict(tags): tags to be written with the fields
|
||||
|
||||
returns: list(result_connector)
|
||||
"""
|
||||
result_connector = connector
|
||||
result_connector.append({"measurement": measurement,
|
||||
"fields": fields,
|
||||
"tags": tags}
|
||||
)
|
||||
return result_connector
|
||||
|
||||
def base_construct(self, base_value_dict):
|
||||
""" Takes values in a writes them to influxdb
|
||||
|
||||
requires: dict(base_value_dict):
|
||||
{ "speed": float(),
|
||||
"direction": float(),
|
||||
"temperature": float(),
|
||||
"humidity": float()}
|
||||
base weather values being sent each time.
|
||||
Wind dir / strength, internal temp / humi
|
||||
|
||||
returns: list(base_connector)
|
||||
"""
|
||||
base_connector = [
|
||||
{
|
||||
"measurement": "wind",
|
||||
"fields": { "value": base_value_dict['speed'] },
|
||||
"tags": { "type": "speed" }
|
||||
},
|
||||
{
|
||||
"measurement": "wind",
|
||||
"fields": { "value": base_value_dict['direction'] },
|
||||
"tags": { "type": "direction" }
|
||||
},
|
||||
{
|
||||
"measurement": "temphumi",
|
||||
"fields": {
|
||||
"temperature": base_value_dict['temperature'],
|
||||
"humidity": base_value_dict['humidity']
|
||||
},
|
||||
"tags": { "type": "internal" }
|
||||
}]
|
||||
return base_connector
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Main
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
if '__main__':
|
||||
'''
|
||||
2 = Supercap voltage (Vue only)
|
||||
3 = ?
|
||||
4 = UV Index
|
||||
5 = Rain rate
|
||||
6 = Solar radiation
|
||||
7 = Solar Cell output (Vue only)
|
||||
8 = Temperature
|
||||
9 = Wind gust
|
||||
a = Humidity
|
||||
e = Rain
|
||||
|
||||
{'nxt': 64, 'P0': 1020.43, 'lqi': 6, 'b2': 3, 'P': 969.29, 'h': 144, 'Ti': 24.49,
|
||||
'cnt': 1, 'Hhtu': 28.68, 'b4': 129, 'b5': 247, 'b6': 36, 'b7': 255, 'b0': 1, 'b1': 10,
|
||||
'hop': 0, 'b3': 225, 'Thtu': 24.28, 'b8': 255, 'b9': 182, 'rssi': 45}
|
||||
|
||||
'''
|
||||
# TODO, make it work for any USB to serial port
|
||||
|
||||
davis_decoder = davisDecoder()
|
||||
davis_writer = DBwriter()
|
||||
try:
|
||||
with serial.Serial('/dev/ttyUSBdavis', 9600) as davis:
|
||||
# Now, let it run a couple times, end and restart via systemd
|
||||
while tout < 400:
|
||||
line = davis.readline()
|
||||
#print(line)
|
||||
davis_data = 0
|
||||
try:
|
||||
davis_data = eval(line)
|
||||
except SyntaxError as e_syntax:
|
||||
print("ERROR (syntax): {}".format(e_syntax))
|
||||
except TypeError as e_type:
|
||||
print("ERROR (Type): {}".format(e_type))
|
||||
except ValueError as e_value:
|
||||
print("ERROR (Type): {}".format(e_value))
|
||||
if davis_data != 0:
|
||||
# Raw data with every tick
|
||||
raw_header = davis_data['h']
|
||||
decoded_header = davis_decoder.davis_id(raw_header)
|
||||
davis_unit_id = decoded_header['davis_id']
|
||||
davis_packet_id = decoded_header['packet_id']
|
||||
|
||||
# Wind, mothafucka!
|
||||
raw_windspeed = davis_data['b0']
|
||||
raw_winddir = davis_data['b1']
|
||||
wind = davis_decoder.decode_wind({"windspeed": raw_windspeed,
|
||||
"winddir": raw_winddir})
|
||||
# Get data external to the ISS, from local PCB / internal
|
||||
# sensors and create base values for influx writing
|
||||
davis_decoder.load_external_data()
|
||||
influx_weather_write = davis_writer.base_construct(
|
||||
{ "speed": float(wind['speed']),
|
||||
"direction": float(wind['direction']),
|
||||
"temperature": float(davis_decoder.inside_temp),
|
||||
"humidity": float(davis_decoder.inside_hum)}
|
||||
)
|
||||
|
||||
# Wind gusts calculation
|
||||
if davis_packet_id == '0x9':
|
||||
raw_gusts = davis_data['b2']
|
||||
wind.update({"windgust": (raw_gusts * 1.60934)})
|
||||
influx_weather_write = davis_writer.construct(
|
||||
influx_weather_write,
|
||||
"wind",
|
||||
{"value": float(wind['windgust'])},
|
||||
{"type": "windgust"}
|
||||
)
|
||||
|
||||
# 0x8 -> temperature
|
||||
if davis_packet_id == '0x8':
|
||||
raw_temp = (davis_data['b2'] << 8) + davis_data['b3']
|
||||
temp_dict = davis_decoder.decode_temp(raw_temp)
|
||||
temp = float(temp_dict['celsius'])
|
||||
pressure_adjusted = davis_decoder.adjust_pressure(temp)
|
||||
influx_weather_write = davis_writer.construct(
|
||||
influx_weather_write,
|
||||
"temphumi",
|
||||
{"pressure": float(pressure_adjusted)},
|
||||
{"type" : "adjusted"}
|
||||
)
|
||||
influx_weather_write = davis_writer.construct(
|
||||
influx_weather_write,
|
||||
"temphumi",
|
||||
{"pressure": float(davis_decoder.pressure)},
|
||||
{"type" : "raw"}
|
||||
)
|
||||
|
||||
influx_weather_write = davis_writer.construct(
|
||||
influx_weather_write,
|
||||
"temphumi",
|
||||
{"temperature": float(temp)},
|
||||
{"type": "external"}
|
||||
)
|
||||
|
||||
# 0xa -> Humidity
|
||||
if davis_packet_id == '0xa':
|
||||
raw_humidity = (((davis_data['b3'] >> 4) & 0b0011) << 8)\
|
||||
+ davis_data['b2']
|
||||
humidity = round(int(raw_humidity) / float(10), 1)
|
||||
influx_weather_write = davis_writer.construct(
|
||||
influx_weather_write,
|
||||
"temphumi",
|
||||
{"humidity": float(humidity)},
|
||||
{"type": "external"}
|
||||
)
|
||||
|
||||
# 0x2 -> SuperCap charge
|
||||
if davis_packet_id == '0x2':
|
||||
supercap = davis_decoder.supercap_decode(
|
||||
davis_data['b2'], davis_data['b3']
|
||||
)
|
||||
influx_status_write = davis_writer.construct(
|
||||
influx_status_write,
|
||||
"iss",
|
||||
{"voltage": float(supercap)},
|
||||
{"type": "capcaitor"}
|
||||
)
|
||||
|
||||
# 0x7 -> SolarPanel Voltage
|
||||
if davis_packet_id == '0x7':
|
||||
solarvolt = davis_decoder.solarvolt_decode(
|
||||
davis_data['b2'], davis_data['b3']
|
||||
)
|
||||
influx_status_write = davis_writer.construct(
|
||||
influx_status_write,
|
||||
"iss",
|
||||
{"voltage": float(solarvolt)},
|
||||
{"type": "solar"}
|
||||
)
|
||||
|
||||
# 0xe -> Rain bucket tips -> https://www.carluccio.de/
|
||||
if davis_packet_id == '0xe':
|
||||
#raw_rain = (davis_data['b2'] << 8) + (davis_data['b3'] >> 7)
|
||||
raw_rain = davis_data['b2']
|
||||
rain = davis_decoder.rain_decode(raw_rain)
|
||||
influx_weather_write = davis_writer.construct(
|
||||
influx_weather_write,
|
||||
"rain",
|
||||
{"value": float(rain)},
|
||||
{"type": "rain_bucket_tips"}
|
||||
)
|
||||
|
||||
# 0x5 -> Rain rate -> https://www.carluccio.de/
|
||||
if davis_packet_id == '0x5':
|
||||
rainrate_dict = davis_decoder.rainrate_decode(
|
||||
davis_data['b3'],
|
||||
davis_data['b4']
|
||||
)
|
||||
rainstate = rainrate_dict['state']
|
||||
rainrate = rainrate_dict['rate']
|
||||
influx_weather_write = davis_writer.construct(
|
||||
influx_weather_write,
|
||||
"rain",
|
||||
{"value": float(rainrate_dict['state'])},
|
||||
{"type": "rainstate"}
|
||||
)
|
||||
influx_weather_write = davis_writer.construct(
|
||||
influx_weather_write,
|
||||
"rain",
|
||||
{"value": float(rainrate_dict['rate'])},
|
||||
{"type": "rainrate"}
|
||||
)
|
||||
|
||||
|
||||
|
||||
out_id = decoded_header
|
||||
out1 = "temp: {}, wind: {}, humidity: {} ".format(
|
||||
temp,
|
||||
wind,
|
||||
humidity)
|
||||
out2 = "Padj: {}, Praw {}, Tins: {}, Humins: {}".format(
|
||||
pressure_adjusted,
|
||||
davis_decoder.pressure,
|
||||
davis_decoder.inside_temp,
|
||||
davis_decoder.inside_hum)
|
||||
out3 = "RainState: {}, Rrate {}, Rain Total: {}, Cap:{}, Volt: {}".format(
|
||||
rainstate,
|
||||
rainrate,
|
||||
rain,
|
||||
supercap,
|
||||
solarvolt)
|
||||
|
||||
print("\n{} \n{} \n{} \n{}\n".format(out_id, out1, out2, out3))
|
||||
|
||||
# Write the whole blob into Influx DB
|
||||
influx_weather_client.write_points(influx_weather_write)
|
||||
influx_status_client.write_points(influx_status_write)
|
||||
tout = tout + 1
|
||||
time.sleep(1)
|
||||
else:
|
||||
print("No data here, mate.")
|
||||
except serial.serialutil.SerialException as e:
|
||||
print("Serial Error {}".format(e))
|
||||
|
||||
120
python/lib/ventilLogger.py
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/python -u
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Type: Python 3.x module
|
||||
Author: Milan Toman (milan.v.toman@gmail.com)
|
||||
Description: Logger module, writes debug, logs and warning into ./log dir
|
||||
Used many times, doesn't change, let us create a module.
|
||||
Module is not ready, needs work.
|
||||
|
||||
TODO:
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Import libraries
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
|
||||
import os
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
class ventilLogger(object):
|
||||
|
||||
def __init__(self):
|
||||
__name__ = 'Zi logger! By Ventil!!!'
|
||||
|
||||
def all_other_shit(self):
|
||||
# Check log directory and create if non-existent
|
||||
if os.path.isdir(_LOG_DIR):
|
||||
# print "INFO: Log directory \"{}\" exists.".format(_LOG_DIR)
|
||||
files = os.listdir(_LOG_DIR)
|
||||
logfile_dict = {}
|
||||
for file in files:
|
||||
if _LOG_FILE_ROOT in file:
|
||||
file_path = os.path.join(_LOG_DIR, file)
|
||||
file_stats = os.stat(file_path)
|
||||
file_mtime = file_stats.st_mtime
|
||||
"""
|
||||
if datetime.datetime.now() - \
|
||||
file_stats.st_mtime > datetime.timedelta(hours=24)
|
||||
"""
|
||||
try:
|
||||
logfile_dict.update({file_path: file_mtime})
|
||||
except:
|
||||
logfile_dict = {file_path: file_mtime}
|
||||
else:
|
||||
pass
|
||||
sorted_list_keys = sorted(logfile_dict, key=logfile_dict.get)
|
||||
# select the last 30 log files to keep, delete the rest.
|
||||
files_to_keep = sorted_list_keys[-30:]
|
||||
for filename in sorted_list_keys:
|
||||
if filename not in files_to_keep:
|
||||
#print("Deleting {}".format(filename))
|
||||
os.remove(filename)
|
||||
else:
|
||||
#print("Not deleting {}".format(filename))
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
os.mkdir(_LOG_DIR)
|
||||
# print "INFO: Created logging directory \"{}\"".format(_LOG_DIR)
|
||||
except () as error:
|
||||
print(u"FATAL: Unable to create " +\
|
||||
u"logging directory \"{}\"".format(_LOG_DIR))
|
||||
raise SystemError(u"Unable to create log directory %s", error)
|
||||
|
||||
# Check for previous logs and rename if any
|
||||
if os.path.isfile(_LOG_FILE):
|
||||
timestapmp_logfile = os.path.getmtime(_LOG_FILE)
|
||||
date_logfile = datetime.datetime.fromtimestamp(timestapmp_logfile)
|
||||
_LOG_RENAME = _LOG_FILE + "." + date_logfile.strftime("%Y%m%d%H%M%S")
|
||||
os.rename(_LOG_FILE, _LOG_RENAME)
|
||||
if os.path.isfile(_DEBUG_FILE):
|
||||
timestapmp_logfile = os.path.getmtime(_DEBUG_FILE)
|
||||
date_logfile = datetime.datetime.fromtimestamp(timestapmp_logfile)
|
||||
_DEBUG_RENAME = _DEBUG_FILE + "." + date_logfile.strftime("%Y%m%d%H%M%S")
|
||||
os.rename(_DEBUG_FILE, _DEBUG_RENAME)
|
||||
|
||||
# Cleanup if more than _MAX_LOGS / _MAX_LOGS_SIZE logs are present
|
||||
# TODO
|
||||
|
||||
# Setup formatting
|
||||
_basic_format = "%(asctime)s %(name)s %(levelname)s %(message)s"
|
||||
_basic_formatter = logging.Formatter(_basic_format)
|
||||
_debug_format = "%(asctime)s %(name)s[%(process)d] \
|
||||
(%(funcName)s) %(levelname)s %(message)s"
|
||||
_debug_formatter = logging.Formatter(_debug_format)
|
||||
_console_format = "%(name)s %(levelname)s: %(message)s"
|
||||
_console_formatter = logging.Formatter(_console_format)
|
||||
|
||||
# Make logging readable with module hierarchy
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# Setting up handlers for stdout / file logging and debug
|
||||
# Logfile
|
||||
basic_handler = logging.FileHandler(_LOG_FILE)
|
||||
basic_handler.setLevel(logging.ERROR)
|
||||
basic_handler.setFormatter(_basic_formatter)
|
||||
logger.addHandler(basic_handler)
|
||||
|
||||
# Debug file
|
||||
debug_handler = logging.FileHandler(_DEBUG_FILE)
|
||||
debug_handler.setLevel(logging.DEBUG)
|
||||
debug_handler.setFormatter(_debug_formatter)
|
||||
logger.addHandler(debug_handler)
|
||||
|
||||
# Console
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.CRITICAL)
|
||||
console_handler.setFormatter(_console_formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# Just for debugging
|
||||
# print _LOG_FILE, _DEBUG_FILE
|
||||
# logger.debug(u'debug message')
|
||||
# logger.info(u'info message')
|
||||
# logger.warn(u'warn message')
|
||||
# logger.error(u'error message')
|
||||
# logger.critical(u'critical message')
|
||||
220
python/sysstats.py
Executable file
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/python -u
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Type: Python 3.x script
|
||||
Author: Milan Toman (milan.v.toman@gmail.com)
|
||||
Description: System stats
|
||||
|
||||
TODO: CPU util.
|
||||
|
||||
|
||||
influxDB SCHEMA:
|
||||
|
||||
DB status
|
||||
|
||||
ISS measure
|
||||
----------------
|
||||
voltage | solar or capacitor | state / lqi / | battery or future_shit |
|
||||
----------------------------------------------------------------
|
||||
field tag field tag
|
||||
|
||||
RasPI
|
||||
----------------
|
||||
usage | disk, mem, cpu, eth, wifi %
|
||||
------------------------------------
|
||||
field | tag
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Import libraries
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
# mandatory
|
||||
import requests
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
import argparse
|
||||
import time
|
||||
import datetime
|
||||
import simplejson as json
|
||||
import influxdb
|
||||
import psutil
|
||||
|
||||
# optionally, future modules, locally available, I hate dependencies
|
||||
from pprint import pprint
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
sys.path.append(_SCRIPT_PATH + "/home/pi/test/lib")
|
||||
#print(_SCRIPT_PATH + "/lib")
|
||||
|
||||
#import ventilLogger
|
||||
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Define variables
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
_VERSION = 2.0
|
||||
_NAME = u"Vantage Vue Decoding shite"
|
||||
_LOG_DIR = _SCRIPT_PATH + '/log/'
|
||||
_LOG_FILE_ROOT = re.sub(u'./', '', sys.argv[0])
|
||||
_LOG_FILE = _LOG_DIR + _LOG_FILE_ROOT + u'.log'
|
||||
_DEBUG_FILE = _LOG_DIR + _LOG_FILE_ROOT + u'.dbg'
|
||||
|
||||
influx_status_write = []
|
||||
|
||||
influx_host = 'localhost'
|
||||
influx_port = 8086
|
||||
influx_user = 'pi'
|
||||
influx_pwd = 'freedavis'
|
||||
status_db = 'status'
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Set up logging - disabled, need to enable this iin future
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Setup arguments and Options - not edited, sample shite
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
desc = u'''\
|
||||
DESCRIPTION:
|
||||
Vantage Vue wireless data transfer decoder, V2
|
||||
consult http://wp.spoton.cz/2017/11/24/davis-vantague-vue-arduino-and-a-raspberry-pi-3/
|
||||
for wtf is going on
|
||||
'''
|
||||
epi = u'''\
|
||||
ERROR CODES:
|
||||
?
|
||||
|
||||
EXAMPLES:
|
||||
?
|
||||
|
||||
'''
|
||||
formatter = argparse.RawDescriptionHelpFormatter
|
||||
arg_parser = argparse.ArgumentParser(description = desc,
|
||||
formatter_class = formatter,
|
||||
epilog = textwrap.dedent(epi))
|
||||
|
||||
arg_parser.add_argument('-d', '--details',
|
||||
help = 'help',
|
||||
action='store_true')
|
||||
arg_parser.add_argument('-v', '--verbose',
|
||||
help = 'help',
|
||||
action='store_true')
|
||||
arg_parser.add_argument('-p', '--section',
|
||||
dest = 'section',
|
||||
default = ['last', 'count', 'diff'],
|
||||
choices = ['last', 'count', 'diff'],
|
||||
nargs = '+',
|
||||
type = str,
|
||||
help = 'help')
|
||||
arg_parser.add_argument('-s', '--snapusage',
|
||||
help = 'help',
|
||||
action='store_true')
|
||||
|
||||
args = arg_parser.parse_args()
|
||||
if args.details:
|
||||
_details = True
|
||||
else:
|
||||
_details = False
|
||||
if args.verbose:
|
||||
_more_details = True
|
||||
else:
|
||||
_more_details = False
|
||||
if args.snapusage:
|
||||
_SNAP_USAGE = True
|
||||
else:
|
||||
_SNAP_USAGE = False
|
||||
try:
|
||||
_sections = args.sections
|
||||
except:
|
||||
_sections = ['last', 'count', 'diff']
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Generic, standalone functions
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
|
||||
# Obvious shit, set up the client class
|
||||
influx_status_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, status_db
|
||||
)
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Classes
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
|
||||
class DBwriter(object):
|
||||
def __init__(self):
|
||||
__name__ = "Database writer class, Influx"
|
||||
|
||||
def construct(self, connector, measurement, fields, tags):
|
||||
""" Takes values in a writes them to influxdb
|
||||
|
||||
requires: list(connector): connector with all ticks to be written
|
||||
at once
|
||||
str(measurement): the measurement ID to be written
|
||||
dict(fields): fields to be written in one tick
|
||||
dict(tags): tags to be written with the fields
|
||||
|
||||
returns: list(result_connector)
|
||||
"""
|
||||
result_connector = connector
|
||||
result_connector.append({"measurement": measurement,
|
||||
"fields": fields,
|
||||
"tags": tags}
|
||||
)
|
||||
return result_connector
|
||||
|
||||
'''
|
||||
--------------------------------------------------------------------------------
|
||||
Main
|
||||
--------------------------------------------------------------------------------
|
||||
'''
|
||||
if '__main__':
|
||||
davis_writer = DBwriter()
|
||||
while True:
|
||||
averaged_cpu = 0
|
||||
# CPU stats
|
||||
for timeout in range(1,5):
|
||||
if timeout == 1:
|
||||
averaged_cpu = psutil.cpu_percent()
|
||||
mem_consumption = psutil.virtual_memory()[2]
|
||||
disk_usage = psutil.disk_usage('/')[3]
|
||||
else:
|
||||
averaged_cpu = (averaged_cpu + psutil.cpu_percent()) / 2
|
||||
time.sleep(1)
|
||||
# Write the whole blob into Influx DB
|
||||
influx_status_write = davis_writer.construct(
|
||||
influx_status_write,
|
||||
"RasPI",
|
||||
{"usage": float(averaged_cpu)},
|
||||
{"type": "cpu"}
|
||||
)
|
||||
influx_status_write = davis_writer.construct(
|
||||
influx_status_write,
|
||||
"RasPI",
|
||||
{"usage": float(mem_consumption)},
|
||||
{"type": "mem"}
|
||||
)
|
||||
influx_status_write = davis_writer.construct(
|
||||
influx_status_write,
|
||||
"RasPI",
|
||||
{"usage": float(disk_usage)},
|
||||
{"type": "disk"}
|
||||
)
|
||||
print(influx_status_write)
|
||||
influx_status_client.write_points(influx_status_write)
|
||||
influx_status_write = []
|
||||
averaged_cpu = 0
|
||||
29
readme.md
Normal file
@@ -0,0 +1,29 @@
|
||||
#FreeDavis project
|
||||
This project aims to provide a user-installable, idiot-proof and reliable way
|
||||
of accessing data provided by the instruments made by a company called Davis.
|
||||
The outdoor units are pretty accurate and reliable units, but to be honest,
|
||||
the inability to access / dump / mine / connect to the data directly, is a limiting
|
||||
factor.
|
||||
|
||||
##Desired functionality
|
||||
As the name suggests, the whole thing should be an open system, based on open software.
|
||||
From the things that the system should be capable of:
|
||||
1. receiving the data from outdoor sensors (obviously)
|
||||
2. Logging and storing the data
|
||||
3. Display / analysis / plotting
|
||||
4. Possibly sending of data
|
||||
|
||||
##Data receiver
|
||||
Data receiver part is accomplished by a CC1101 wireless chip connected to an
|
||||
Arduino mini, both working on 3.3V. Dtat is being sent via serial Rx Tx pins of
|
||||
the arduino.
|
||||
|
||||
##Data logger
|
||||
Current working solution incorporates an additional connection of a RaspberryPi
|
||||
that loggs data sent by the Arduino via serial interface.
|
||||
- data extraction written in python
|
||||
- data feeder written in python (influxDB connector)
|
||||
- data logging and storing is accomplished via InfluxDB (subject to change?)
|
||||
- data serving python app based on cherrypy
|
||||
- UI based on bootstrap3
|
||||
- graphing by Dygraphs.js
|
||||
2
web/TODO.txt
Executable file
@@ -0,0 +1,2 @@
|
||||
Absolute web path throughout the project
|
||||
Graphs share JS variables, separate them.
|
||||
50
web/chttpd.py
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import cherrypy
|
||||
import OpenSSL
|
||||
import sys
|
||||
|
||||
PATH = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
sys.path.append(_SCRIPT_PATH)
|
||||
|
||||
import voltage
|
||||
import weather
|
||||
import dynamic
|
||||
import status
|
||||
|
||||
server_config={
|
||||
'server.socket_host': '0.0.0.0',
|
||||
'server.socket_port':443,
|
||||
'server.ssl_module':'builtin',
|
||||
'server.ssl_certificate':'/home/www/.cert/fullchain1.pem',
|
||||
'server.ssl_private_key':'/home/www/.cert/privkey1.pem'
|
||||
}
|
||||
cherrypy.config.update(server_config)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
|
||||
|
||||
conf = {
|
||||
'/': {
|
||||
'tools.sessions.on': True,
|
||||
'tools.staticdir.root': os.path.abspath(_SCRIPT_PATH + '/')
|
||||
},
|
||||
'/static': {
|
||||
'tools.staticdir.on': True,
|
||||
'tools.staticdir.dir': './static'
|
||||
},
|
||||
'/data': {
|
||||
'tools.staticdir.on': False,
|
||||
'tools.staticdir.dir': './dynamic'
|
||||
}
|
||||
}
|
||||
|
||||
cherrypy.tree.mount(voltage.EnergyInfo(), "/", conf)
|
||||
cherrypy.tree.mount(weather.WeatherInfo(), "/weather", conf)
|
||||
cherrypy.tree.mount(status.StatusInfo(), "/status", conf)
|
||||
cherrypy.tree.mount(dynamic.DynamicData(), "/data", conf)
|
||||
cherrypy.engine.start()
|
||||
cherrypy.engine.block()
|
||||
497
web/dynamic.py
Executable file
@@ -0,0 +1,497 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import cherrypy
|
||||
import influxdb
|
||||
import time
|
||||
import datetime
|
||||
import json
|
||||
from cherrypy.lib.httputil import parse_query_string
|
||||
|
||||
|
||||
# Universal variables
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
|
||||
influx_host = 'localhost'
|
||||
influx_port = 8086
|
||||
influx_user = 'pi'
|
||||
influx_pwd = 'freedavis'
|
||||
influx_db = 'voltage'
|
||||
influx_weather_db = 'weather'
|
||||
influx_status_db = 'status'
|
||||
|
||||
variables_known = ["range", "granularity", "start", "end"]
|
||||
default_variables = {"range": "1h", "granularity": "30s", "end": "1s"}
|
||||
|
||||
|
||||
class DynamicData(object):
|
||||
def __init__(self):
|
||||
self.influx_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_db
|
||||
)
|
||||
self.influx_weather_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_weather_db
|
||||
)
|
||||
self.influx_status_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_status_db
|
||||
)
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return "Index, MOFO"
|
||||
|
||||
def check_GET(self, arguments):
|
||||
''' No other purpose than to make it more secure and only process the
|
||||
values that are meant to be processed. Hardcoded on top, mate!
|
||||
|
||||
sets:
|
||||
list(): key values of those which should be processed, merged
|
||||
with deault values
|
||||
'''
|
||||
self.q = eval(str(arguments))
|
||||
keys_to_process = {
|
||||
key:self.q[key] for key in self.q.keys() if key in variables_known}
|
||||
resulting_variables = default_variables.copy()
|
||||
resulting_variables.update(keys_to_process)
|
||||
self.q = resulting_variables
|
||||
|
||||
@cherrypy.expose
|
||||
def solar_monitor(self, **kwargs):
|
||||
'''
|
||||
Function to get solar readings from InfluxDB.
|
||||
These parsed into a CSV
|
||||
|
||||
yields: csv in raw, text format
|
||||
time, V_solar, I_solar, P_solar
|
||||
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
self.check_GET(kwargs)
|
||||
query1 = "SELECT mean(V_solar) as Usol, mean(I_solar) as Isol, "
|
||||
query2 = "mean(V_array) as Varr, mean(I_consumption) as Icons FROM voltage "
|
||||
query3 = "WHERE time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
query4 = "ORDER BY time DESC"
|
||||
query = "{} {} {} {}".format(query1, query2, query3, query4)
|
||||
measures = self.influx_client.query(query)
|
||||
# Let's get the data from DB
|
||||
header = "time,V_solar,I_solar,P_solar,P_cons\n"
|
||||
yield header
|
||||
for datapoint in measures["voltage"]:
|
||||
tm = str(datapoint["time"]).strip()
|
||||
solar_voltage = str(datapoint["Usol"]).strip()
|
||||
solar_current = str(datapoint["Isol"]).strip()
|
||||
array_voltage = str(datapoint["Varr"]).strip()
|
||||
consumption_current = str(datapoint["Icons"]).strip()
|
||||
if solar_voltage != 'None' and solar_current != 'None' \
|
||||
and array_voltage != 'None' and consumption_current != 'None':
|
||||
solar_voltage = float(solar_voltage) / 1000.00
|
||||
solar_current = float(solar_current) / 1000.00
|
||||
array_voltage = float(array_voltage) / 1000.00
|
||||
consumption_current = float(consumption_current) / 1000.00
|
||||
else:
|
||||
solar_voltage = 0.00
|
||||
solar_current = 0.00
|
||||
array_voltage = 0.00
|
||||
consumption_current = 0.00
|
||||
# The 8W is the approximate internal consumption of the mppt controller ~ 0.15A
|
||||
# This value was removed, No idea why it appeared there in the first place.
|
||||
solar_power = round(solar_voltage * solar_current, 2)
|
||||
consumption_power = round(array_voltage * consumption_current, 2)
|
||||
yield "{},{},{},{},{}\n".format(tm, solar_voltage,
|
||||
solar_current, solar_power,
|
||||
consumption_power)
|
||||
|
||||
|
||||
@cherrypy.expose
|
||||
def wind_monitor(self, **kwargs):
|
||||
'''
|
||||
Function to get wind value readings from InfluxDB.
|
||||
These parsed into a CSV
|
||||
|
||||
yields: csv in raw, text format
|
||||
time, Speed, Gusts, Direction
|
||||
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
self.check_GET(kwargs)
|
||||
speed_q1 = "SELECT mean(value) as w_speed FROM wind"
|
||||
gust_q1 = "SELECT mean(value) as w_gust FROM wind"
|
||||
direction_q1 = "SELECT mean(value) as w_dir FROM wind"
|
||||
speed_q2 = "WHERE time > NOW() - {} AND time < NOW() - {} AND type = 'speed' GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
gust_q2 = "WHERE time > NOW() - {} AND time < NOW() - {} AND type = 'windgust' GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
direction_q2 = "WHERE time > NOW() - {} AND time < NOW() - {} AND type = 'direction' GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
q3 = "ORDER BY time DESC"
|
||||
speed_query = "{} {} {}".format(speed_q1, speed_q2, q3)
|
||||
gust_query = "{} {} {}".format(gust_q1, gust_q2, q3)
|
||||
direction_query = "{} {} {}".format(direction_q1, direction_q2, q3)
|
||||
rs_speed = self.influx_weather_client.query(speed_query)
|
||||
rs_gust = self.influx_weather_client.query(gust_query)
|
||||
rs_direction = self.influx_weather_client.query(direction_query)
|
||||
# Let's get the data from DB
|
||||
header = "time,Speed,Gusts,Direction\n"
|
||||
yield header
|
||||
for speed, gust, direction in zip(rs_speed['wind'], rs_gust['wind'], rs_direction['wind']):
|
||||
tm_speed = str(speed["time"]).strip()
|
||||
tm_gust = str(gust["time"]).strip()
|
||||
tm_direction = str(direction["time"]).strip()
|
||||
speed_value = str(speed["w_speed"]).strip()
|
||||
gust_value = str(gust["w_gust"]).strip()
|
||||
direction_value = str(direction["w_dir"]).strip()
|
||||
#if tm_speed == tm_gust and tm_speed == tm_direction:
|
||||
#tm = strptime(speed["time"]).strip(), "%Y-%m-%dT%H:%M:%SZ")
|
||||
yield "{},{},{},{}\n".format(tm_speed, speed_value, gust_value, direction_value)
|
||||
|
||||
@cherrypy.expose
|
||||
def temphumi_monitor(self, **kwargs):
|
||||
'''
|
||||
Function to get temperature and humidity readings from InfluxDB.
|
||||
These parsed into a CSV
|
||||
|
||||
yields: csv in raw, text format
|
||||
time,
|
||||
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
self.check_GET(kwargs)
|
||||
temp_q1 = "SELECT mean(temperature) as temp FROM temphumi"
|
||||
hum_q1 = "SELECT mean(humidity) as hum FROM temphumi"
|
||||
in_q2 = "WHERE time > NOW() - {} AND time < NOW() - {} AND type = 'internal' GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
out_q2 = "WHERE time > NOW() - {} AND time < NOW() - {} AND type = 'external' GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
q3 = "ORDER BY time DESC"
|
||||
temp_in_query = "{} {} {}".format(temp_q1, in_q2, q3)
|
||||
temp_out_query = "{} {} {}".format(temp_q1, out_q2, q3)
|
||||
hum_in_query = "{} {} {}".format(hum_q1, in_q2, q3)
|
||||
hum_out_query = "{} {} {}".format(hum_q1, out_q2, q3)
|
||||
rs_temp_in = self.influx_weather_client.query(temp_in_query)
|
||||
rs_temp_out = self.influx_weather_client.query(temp_out_query)
|
||||
rs_hum_in = self.influx_weather_client.query(hum_in_query)
|
||||
rs_hum_out = self.influx_weather_client.query(hum_out_query)
|
||||
|
||||
# Let's get the data from DB
|
||||
header = "time,T(ins),T(out),Humi(ins),Humi(out)\n"
|
||||
yield header
|
||||
for Tin, Tout, Hin, Hout in zip(rs_temp_in['temphumi'],
|
||||
rs_temp_out['temphumi'],
|
||||
rs_hum_in['temphumi'],
|
||||
rs_hum_out['temphumi']):
|
||||
tm_temp = str(Tin["time"]).strip()
|
||||
temp_in_val = str(Tin["temp"]).strip()
|
||||
temp_out_val = str(Tout["temp"]).strip()
|
||||
hum_in_val = str(Hin["hum"]).strip()
|
||||
hum_out_val = str(Hout["hum"]).strip()
|
||||
#if tm_speed == tm_gust and tm_speed == tm_direction:
|
||||
#tm = strptime(speed["time"]).strip(), "%Y-%m-%dT%H:%M:%SZ")
|
||||
yield "{},{},{},{},{}\n".format(tm_temp, temp_in_val, temp_out_val, hum_in_val, hum_out_val)
|
||||
|
||||
@cherrypy.expose
|
||||
def pressure_monitor(self, **kwargs):
|
||||
'''
|
||||
Function to get pressure readings from InfluxDB.
|
||||
These parsed into a CSV
|
||||
|
||||
yields: csv in raw, text format
|
||||
time, Pressure
|
||||
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
self.check_GET(kwargs)
|
||||
query1 = "SELECT mean(pressure) as pressure FROM temphumi"
|
||||
query2 = "WHERE type = 'adjusted' AND time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
query3 = "ORDER BY time DESC"
|
||||
query = "{} {}".format(query1, query2, query3)
|
||||
measures = self.influx_weather_client.query(query)
|
||||
# Let's get the data from DB
|
||||
header = "time,Pressure\n"
|
||||
yield header
|
||||
for datapoint in measures["temphumi"]:
|
||||
tm = str(datapoint["time"]).strip()
|
||||
pressure = str(datapoint["pressure"]).strip()
|
||||
yield "{},{}\n".format(tm, pressure)
|
||||
|
||||
|
||||
@cherrypy.expose
|
||||
def solcap_monitor(self, **kwargs):
|
||||
'''
|
||||
Function to get solar and supercap readings from InfluxDB.
|
||||
These parsed into a CSV
|
||||
|
||||
yields: csv in raw, text format
|
||||
time, Solar Irradiance, Capacitor
|
||||
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
self.check_GET(kwargs)
|
||||
solar_query1 = "SELECT mean(voltage) as solar FROM iss"
|
||||
solar_query2 = "WHERE type = 'solar' AND time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
cap_query1 = "SELECT mean(voltage) as cap FROM iss"
|
||||
cap_query2 = "WHERE type = 'capcaitor' AND time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
query3 = "ORDER BY time DESC"
|
||||
query_solar = "{} {} {}".format(solar_query1, solar_query2, query3)
|
||||
query_cap = "{} {} {}".format(cap_query1, cap_query2, query3)
|
||||
measures_sol = self.influx_status_client.query(query_solar)
|
||||
measures_cap = self.influx_status_client.query(query_cap)
|
||||
# Let's get the data from DB
|
||||
header = "time,Solar,Capacitor\n"
|
||||
yield header
|
||||
for Sol, Cap in zip(measures_sol['iss'], measures_cap['iss']):
|
||||
tm = str(Sol["time"]).strip()
|
||||
try:
|
||||
solar_value = float(str(Sol["solar"]).strip()) / 100
|
||||
except:
|
||||
solar_value = ''
|
||||
try:
|
||||
cap_value = float(str(Cap["cap"]).strip())
|
||||
except:
|
||||
cap_value = ''
|
||||
yield "{},{},{}\n".format(tm, solar_value, cap_value)
|
||||
|
||||
|
||||
@cherrypy.expose
|
||||
def cpumem_monitor(self, **kwargs):
|
||||
'''
|
||||
Function to get cpu,mem, disk % from InfluxDB.
|
||||
These parsed into a CSV
|
||||
|
||||
yields: csv in raw, text format
|
||||
time, Cpu Mem, Disk
|
||||
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
self.check_GET(kwargs)
|
||||
cpu_query1 = "SELECT mean(usage) as Cpu "
|
||||
mem_query1 = "SELECT mean(usage) as Mem "
|
||||
disk_query1 = "SELECT mean(usage) as Disk "
|
||||
query2 = "FROM RasPI "
|
||||
cpu_query3 = "WHERE type = 'cpu' AND time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
mem_query3 = "WHERE type = 'mem' AND time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
disk_query3 = "WHERE type = 'disk' AND time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
query4 = "ORDER BY time DESC"
|
||||
query_cpu = "{} {} {} {}".format(cpu_query1, query2, cpu_query3, query4)
|
||||
query_mem = "{} {} {} {}".format(mem_query1, query2, mem_query3, query4)
|
||||
query_disk = "{} {} {} {}".format(disk_query1, query2, disk_query3, query4)
|
||||
measures_cpu = self.influx_status_client.query(query_cpu)
|
||||
measures_mem = self.influx_status_client.query(query_mem)
|
||||
measures_disk = self.influx_status_client.query(query_disk)
|
||||
# Let's get the data from DB
|
||||
header = "time,Cpu,Mem,Disk\n"
|
||||
yield header
|
||||
for cpu, mem, disk in zip(measures_cpu['RasPI'], measures_mem['RasPI'], measures_disk['RasPI']):
|
||||
tm = str(cpu["time"]).strip()
|
||||
try:
|
||||
cpu_value = float(str(cpu["Cpu"]).strip())
|
||||
except:
|
||||
solar_value = ''
|
||||
try:
|
||||
mem_value = float(str(mem["Mem"]).strip())
|
||||
except:
|
||||
cap_value = ''
|
||||
try:
|
||||
disk_value = float(str(disk["Disk"]).strip())
|
||||
except:
|
||||
disk_value = ''
|
||||
yield "{},{},{},{}\n".format(tm, cpu_value, mem_value, disk_value)
|
||||
|
||||
|
||||
@cherrypy.expose
|
||||
def historical_values(self, **kwargs):
|
||||
'''
|
||||
Function to all historical readings from InfluxDB.
|
||||
These parsed into a CSV? Dict?
|
||||
|
||||
returns: csv in raw, text format
|
||||
time, V_solar, I_solar
|
||||
|
||||
select mean(I_solar) as I_solar from voltage where time > now() - 10m group by time(30s) order by time desc
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
|
||||
measure_range = kwargs['range']
|
||||
query1 = "SELECT mean(V_array) as Uarr, mean(I_consumption) as Icon, "
|
||||
query2 = "mean(V_solar) as Usol, mean(I_solar) as Isol, "
|
||||
query3 = "mean(V_unit1) as Us1, mean(V_unit2) as Us2, max(charging) as charge FROM voltage"
|
||||
query4 = "WHERE time > NOW() - {} GROUP BY time(30s)".format(measure_range)
|
||||
query5 = "ORDER BY time DESC"
|
||||
query = "{} {} {} {} {}".format(query1, query2, query3, query4, query5)
|
||||
measures = self.influx_client.query(query)
|
||||
|
||||
# Let's get the data from DB
|
||||
result = []
|
||||
for datapoint in measures["voltage"]:
|
||||
tm = datapoint['time']
|
||||
try:
|
||||
array_voltage = round(float(datapoint["Uarr"]), 2)
|
||||
current_consumed = round(float(datapoint["Icon"]) / array_voltage, 2)
|
||||
|
||||
solar_voltage = round(float(datapoint["Usol"]), 2)
|
||||
solar_current = round(float(datapoint["Isol"]) / solar_voltage , 2)
|
||||
|
||||
Us1 = round(float(datapoint["Us1"]), 2)
|
||||
Us2 = round(float(datapoint["Us2"]), 2)
|
||||
except:
|
||||
continue
|
||||
|
||||
charging = int(datapoint["charge"])
|
||||
row = {"time": tm,
|
||||
"V_array": array_voltage,
|
||||
"I_consumption": current_consumed,
|
||||
"V_solar": solar_voltage,
|
||||
"I_solar": solar_current,
|
||||
"V_unit1": Us1,
|
||||
"V_unit2": Us2,
|
||||
"charging": charging}
|
||||
result.append(row)
|
||||
return result
|
||||
|
||||
@cherrypy.expose
|
||||
def stat_values(self, **kwargs):
|
||||
'''
|
||||
Function to all historical readings from InfluxDB.
|
||||
These parsed into a CSV? Dict?
|
||||
|
||||
returns: csv in raw, text format
|
||||
24Wh_consumed, 24Wh_solar
|
||||
|
||||
select mean(I_solar) as I_solar from voltage where time > now() - 10m group by time(30s) order by time desc
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
|
||||
_days = kwargs['days']
|
||||
d = datetime.datetime.now()
|
||||
measures = []
|
||||
result = []
|
||||
|
||||
for t_range in range(1, _days*24, 24):
|
||||
day = (d - datetime.timedelta(hours = t_range)).strftime("%Y-%m-%d")
|
||||
query1 = "SELECT mean(V_array) as Uarr, mean(I_consumption) as Icon, "
|
||||
query2 = "mean(V_solar) as Usol, mean(I_solar) as Isol, "
|
||||
query3 = "mean(V_unit1) as Us1, mean(V_unit2) as Us2, max(charging) as charge FROM voltage"
|
||||
query4 = "WHERE time > '{} 00:00:00' AND time < '{} 23:59:59' GROUP BY time(1h) fill(0)".format(day, day)
|
||||
query5 = "ORDER BY time DESC"
|
||||
query = "{} {} {} {} {}".format(query1, query2, query3, query4, query5)
|
||||
measure = self.influx_client.query(query)
|
||||
|
||||
# Let's get the data from DB
|
||||
tm = []
|
||||
Ubat = []
|
||||
Icon = []
|
||||
Usol = []
|
||||
Isol = []
|
||||
Uss1 = []
|
||||
Uss2 = []
|
||||
P_cons = []
|
||||
P_sol = []
|
||||
charging = 0
|
||||
day_result = []
|
||||
|
||||
for datapoint in measure["voltage"]:
|
||||
#print(datapoint)
|
||||
|
||||
tstamp = datapoint['time']
|
||||
array_voltage = round(float(datapoint["Uarr"]), 2)
|
||||
current_consumed = round(float(datapoint["Icon"]), 2)
|
||||
solar_voltage = round(float(datapoint["Usol"]), 2)
|
||||
solar_current = round(float(datapoint["Isol"]), 2)
|
||||
Us1 = round(float(datapoint["Us1"]), 2)
|
||||
Us2 = round(float(datapoint["Us2"]), 2)
|
||||
charge = int(datapoint["charge"])
|
||||
p_consumed = round(float(array_voltage / 1000.00 \
|
||||
* current_consumed / 1000.00), 2)
|
||||
p_solar = round(float(solar_voltage / 1000.00 \
|
||||
* solar_current / 1000.00), 2)
|
||||
|
||||
tm.append(tstamp),
|
||||
Ubat.append(array_voltage),
|
||||
Icon.append(current_consumed),
|
||||
Usol.append(solar_voltage),
|
||||
Isol.append(solar_current),
|
||||
Uss1.append(Us1),
|
||||
Uss2.append(Us2),
|
||||
charging = charging + charge
|
||||
P_cons.append(p_consumed)
|
||||
P_sol.append(p_solar)
|
||||
|
||||
row = {"time": tm,
|
||||
"V_array": Ubat,
|
||||
"I_consumption": Icon,
|
||||
"V_solar": Usol,
|
||||
"I_solar": Isol,
|
||||
"V_unit1": Uss1,
|
||||
"V_unit2": Uss2,
|
||||
"charging": charging,
|
||||
"P_cons": P_cons,
|
||||
"P_sol": P_sol}
|
||||
|
||||
try:
|
||||
day_result = [
|
||||
row['time'][0][0:10],
|
||||
round(min(row['V_array']) / 1000, 2),
|
||||
round(sum(row['I_consumption']) / 1000, 2),
|
||||
round(max(row['V_solar']) / 1000, 2),
|
||||
round(sum(row['I_solar']) / 1000, 2),
|
||||
round(min(row['V_unit1']) / 1000, 2),
|
||||
round(min(row['V_unit2']) / 1000, 2),
|
||||
row['charging'],
|
||||
round(sum(row['P_cons']),2),
|
||||
round(sum(row['P_sol']), 2)
|
||||
]
|
||||
except:
|
||||
day_result = [
|
||||
d.strftime("%Y-%m-%d"),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
# need to compute averages, mate...
|
||||
print(day_result)
|
||||
result.append(day_result)
|
||||
return result
|
||||
1339
web/static/css/bootstrap-grid.css
vendored
Normal file
1
web/static/css/bootstrap-grid.css.map
Normal file
1
web/static/css/bootstrap-grid.min.css
vendored
Normal file
1
web/static/css/bootstrap-grid.min.css.map
Normal file
459
web/static/css/bootstrap-reboot.css
vendored
Normal file
@@ -0,0 +1,459 @@
|
||||
/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
footer,
|
||||
header,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
figcaption,
|
||||
figure,
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
hr {
|
||||
-webkit-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
-webkit-text-decoration-skip: objects;
|
||||
}
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none;
|
||||
text-decoration: underline;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
mark {
|
||||
background-color: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
audio,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
line-height: 1.15;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
button,
|
||||
html [type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
legend {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
display: table;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
progress {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="search"]::-webkit-search-cancel-button,
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
details,
|
||||
menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
-webkit-box-sizing: inherit;
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
@-ms-viewport {
|
||||
width: device-width;
|
||||
}
|
||||
|
||||
html {
|
||||
-ms-overflow-style: scrollbar;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
color: #292b2c;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title],
|
||||
abbr[data-original-title] {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0275d8;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:focus, a:hover {
|
||||
color: #014c8c;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a,
|
||||
area,
|
||||
button,
|
||||
[role="button"],
|
||||
input,
|
||||
label,
|
||||
select,
|
||||
summary,
|
||||
textarea {
|
||||
-ms-touch-action: manipulation;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
color: #636c72;
|
||||
text-align: left;
|
||||
caption-side: bottom;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: 1px dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
textarea {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
input[type="radio"]:disabled,
|
||||
input[type="checkbox"]:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
input[type="date"],
|
||||
input[type="time"],
|
||||
input[type="datetime-local"],
|
||||
input[type="month"] {
|
||||
-webkit-appearance: listbox;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||
1
web/static/css/bootstrap-reboot.css.map
Normal file
1
web/static/css/bootstrap-reboot.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}@-ms-viewport{width:device-width}html{-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}body{font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#292b2c;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{cursor:help}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}a{color:#0275d8;text-decoration:none}a:focus,a:hover{color:#014c8c;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle}[role=button]{cursor:pointer}[role=button],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}table{border-collapse:collapse;background-color:transparent}caption{padding-top:.75rem;padding-bottom:.75rem;color:#636c72;text-align:left;caption-side:bottom}th{text-align:left}label{display:inline-block;margin-bottom:.5rem}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,select,textarea{line-height:inherit}input[type=checkbox]:disabled,input[type=radio]:disabled{cursor:not-allowed}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit}input[type=search]{-webkit-appearance:none}output{display:inline-block}[hidden]{display:none!important}/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
||||
1
web/static/css/bootstrap-reboot.min.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../scss/_normalize.scss","bootstrap-reboot.css","../../scss/_reboot.scss","../../scss/_variables.scss","../../scss/mixins/_hover.scss"],"names":[],"mappings":"4EAYA,KACE,YAAA,WACA,YAAA,KACA,qBAAA,KACA,yBAAA,KAUF,KACE,OAAA,EAOF,QAAA,MAAA,OAAA,OAAA,IAAA,QAME,QAAA,MAQF,GACE,UAAA,IACA,OAAA,MAAA,EAWF,WAAA,OAAA,KAGE,QAAA,MAOF,OACE,OAAA,IAAA,KAQF,GACE,mBAAA,YAAA,WAAA,YACA,OAAA,EACA,SAAA,QAQF,IACE,YAAA,UAAA,UACA,UAAA,IAWF,EACE,iBAAA,YACA,6BAAA,QAQF,SAAA,QAEE,cAAA,EAQF,YACE,cAAA,KACA,gBAAA,UACA,gBAAA,UAAA,OAOF,EAAA,OAEE,YAAA,QAOF,EAAA,OAEE,YAAA,OAQF,KAAA,IAAA,KAGE,YAAA,UAAA,UACA,UAAA,IAOF,IACE,WAAA,OAOF,KACE,iBAAA,KACA,MAAA,KAOF,MACE,UAAA,IAQF,IAAA,IAEE,UAAA,IACA,YAAA,EACA,SAAA,SACA,eAAA,SAGF,IACE,OAAA,OAGF,IACE,IAAA,MAUF,MAAA,MAEE,QAAA,aAOF,sBACE,QAAA,KACA,OAAA,EAOF,IACE,aAAA,KAOF,eACE,SAAA,OAWF,OAAA,MAAA,SAAA,OAAA,SAKE,YAAA,WACA,UAAA,KACA,YAAA,KACA,OAAA,EAQF,OAAA,MAEE,SAAA,QAQF,OAAA,OAEE,eAAA,KASF,aAAA,cAAA,OAAA,mBAIE,mBAAA,OAOF,gCAAA,+BAAA,gCAAA,yBAIE,aAAA,KACA,QAAA,EAOF,6BAAA,4BAAA,6BAAA,sBAIE,QAAA,IAAA,OAAA,WAOF,SACE,OAAA,IAAA,MAAA,OACA,OAAA,EAAA,IACA,QAAA,MAAA,OAAA,MAUF,OACE,mBAAA,WAAA,WAAA,WACA,MAAA,QACA,QAAA,MACA,UAAA,KACA,QAAA,EACA,YAAA,OAQF,SACE,QAAA,aACA,eAAA,SAOF,SACE,SAAA,KCrKF,gBAAA,aD+KE,mBAAA,WAAA,WAAA,WACA,QAAA,EC1KF,yCAAA,yCDmLE,OAAA,KC9KF,cDuLE,mBAAA,UACA,eAAA,KCnLF,4CAAA,yCD4LE,mBAAA,KAQF,6BACE,mBAAA,OACA,KAAA,QAWF,QAAA,KAEE,QAAA,MAOF,QACE,QAAA,UAUF,OACE,QAAA,aAOF,SACE,QAAA,KCnNF,SD8NE,QAAA,KEtbF,KACE,mBAAA,WAAA,WAAA,WAGF,EAAA,QAAA,SAGE,mBAAA,QAAA,WAAA,QAoBA,cAAgB,MAAA,aAQlB,KAYE,mBAAA,UAGA,4BAAA,YAGF,KACE,YAAA,cAAA,UAAA,mBAAA,WAAA,OC2K4H,iBD3K5H,MAAA,WACA,UAAA,KACA,YAAA,IACA,YAAA,IAEA,MAAA,QAEA,iBAAA,KD2LF,sBClLE,QAAA,YAYF,GAAI,GAAI,GAAI,GAAI,GAAI,GAClB,WAAA,EACA,cAAA,MAOF,EACE,WAAA,EACA,cAAA,KAIF,0BAAA,YAGE,OAAA,KAGF,QACE,cAAA,KACA,WAAA,OACA,YAAA,QAGF,GAAA,GAAA,GAGE,WAAA,EACA,cAAA,KAGF,MAAA,MAAA,MAAA,MAIE,cAAA,EAGF,GACE,YAAA,IAGF,GACE,cAAA,MACA,YAAA,EAGF,WACE,OAAA,EAAA,EAAA,KAQF,EACE,MAAA,QACA,gBAAA,KEhJE,QAAA,QFmJA,MAAA,QACA,gBAAA,UAUJ,8BACE,MAAA,QACA,gBAAA,KEhKE,oCAAA,oCFmKA,MAAA,QACA,gBAAA,KANJ,oCAUI,QAAA,EASJ,IAEE,WAAA,EAEA,cAAA,KAEA,SAAA,KAQF,OAGE,OAAA,EAAA,EAAA,KAQF,IAGE,eAAA,ODsIF,cCzHE,OAAA,QAcF,cAAA,EAAA,KAAA,OAAA,MAAA,MAAA,OAAA,QAAA,SASE,iBAAA,aAAA,aAAA,aAQF,MAEE,gBAAA,SAEA,iBAAA,YAGF,QACE,YAAA,OACA,eAAA,OACA,MAAA,QACA,WAAA,KACA,aAAA,OAGF,GAEE,WAAA,KAQF,MAEE,QAAA,aACA,cAAA,MAOF,aACE,QAAA,IAAA,OACA,QAAA,IAAA,KAAA,yBAGF,OAAA,MAAA,OAAA,SAME,YAAA,QAGF,8BAAA,2BAMI,OAAA,YAKJ,iBAAA,iBAAA,2BAAA,kBASE,mBAAA,QAGF,SAEE,OAAA,SAGF,SAME,UAAA,EAEA,QAAA,EACA,OAAA,EACA,OAAA,EAGF,OAEE,QAAA,MACA,MAAA,KACA,QAAA,EACA,cAAA,MACA,UAAA,OACA,YAAA,QAGF,mBAKE,mBAAA,KAIF,OACE,QAAA,aDsEF,SC9DE,QAAA"}
|
||||
9320
web/static/css/bootstrap.css
vendored
Normal file
1
web/static/css/bootstrap.css.map
Normal file
6
web/static/css/bootstrap.min.css
vendored
Normal file
1
web/static/css/bootstrap.min.css.map
Normal file
70
web/static/css/dashboard.css
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Base structure
|
||||
*/
|
||||
|
||||
/* Move down content because we have a fixed navbar that is 50px tall */
|
||||
body {
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Typography
|
||||
*/
|
||||
|
||||
h1 {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 9px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sidebar
|
||||
*/
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 51px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
padding: 20px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
|
||||
/* Sidebar navigation */
|
||||
.sidebar {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.sidebar .nav {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sidebar .nav-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar .nav-item + .nav-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dashboard
|
||||
*/
|
||||
|
||||
/* Placeholders */
|
||||
.placeholders {
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
.placeholder img {
|
||||
padding-top: 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
75
web/static/css/def_style.css
Normal file
@@ -0,0 +1,75 @@
|
||||
/*BODY{font-family:monospace; background-color: #fafafa; padding: 0px; margin:0px;}*/
|
||||
div.textbox{
|
||||
width: 50em;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 2em;
|
||||
padding: 3em;
|
||||
border: 1px solid #ccc;
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 2px solid #000;
|
||||
border-right: 2px solid #000;
|
||||
background-color: #fff;
|
||||
}
|
||||
/*H1{font-size: 2em;}
|
||||
H2{font-size: 1em; color: #666;}
|
||||
h3{font-size: 0.8em; coloe: #eee;}
|
||||
TABLE{border:1px solid grey;}
|
||||
TD{padding: 2px;}
|
||||
TH{background-color: #eee;}*/
|
||||
IMG#profile{
|
||||
float: left;
|
||||
margin-right: 2em;
|
||||
margin-bottom: 4em;
|
||||
margin-top:4em;
|
||||
}
|
||||
#header{
|
||||
border-bottom: 2px solid #000;
|
||||
margin-bottom: 4em;
|
||||
padding: 8em;
|
||||
padding-top: 2em;
|
||||
padding-bottom: 1em;
|
||||
background-color: #fff;
|
||||
}
|
||||
#footer{
|
||||
font-size: 0.5em;
|
||||
color: #777;
|
||||
margin: 0px;
|
||||
margin-top: 4em;
|
||||
padding: 10em;
|
||||
padding-top: 2em;
|
||||
padding-bottom: 4em;
|
||||
border-top: 1px solid #ddd;
|
||||
background-color: #fefefe;
|
||||
}
|
||||
#menu{
|
||||
float: right;
|
||||
}
|
||||
.menuitem{
|
||||
float: left;
|
||||
font-size: 1.5em;
|
||||
color: #000;
|
||||
}
|
||||
.menuitem_current{
|
||||
float: left;
|
||||
font-size: 1.5em;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.menuitem a{
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
}
|
||||
.menuitem a:hover{
|
||||
color: #600;
|
||||
}
|
||||
.cleaner{
|
||||
clear: both;
|
||||
display: inline;
|
||||
height: 0px;
|
||||
}
|
||||
#logo{
|
||||
|
||||
}
|
||||
.left{float: left;}
|
||||
.right{float: right;}
|
||||
117
web/static/css/dygraph.css
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Default styles for the dygraphs charting library.
|
||||
*/
|
||||
|
||||
.dygraph-legend {
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
z-index: 10;
|
||||
width: 250px; /* labelsDivWidth */
|
||||
/*
|
||||
dygraphs determines these based on the presence of chart labels.
|
||||
It might make more sense to create a wrapper div around the chart proper.
|
||||
top: 0px;
|
||||
right: 2px;
|
||||
*/
|
||||
background: white;
|
||||
line-height: normal;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* styles for a solid line in the legend */
|
||||
.dygraph-legend-line {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
bottom: .5ex;
|
||||
padding-left: 1em;
|
||||
height: 1px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
/* border-bottom-color is set based on the series color */
|
||||
}
|
||||
|
||||
/* styles for a dashed line in the legend, e.g. when strokePattern is set */
|
||||
.dygraph-legend-dash {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
bottom: .5ex;
|
||||
height: 1px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
/* border-bottom-color is set based on the series color */
|
||||
/* margin-right is set based on the stroke pattern */
|
||||
/* padding-left is set based on the stroke pattern */
|
||||
}
|
||||
|
||||
.dygraph-roller {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* This class is shared by all annotations, including those with icons */
|
||||
.dygraph-annotation {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* This class only applies to annotations without icons */
|
||||
/* Old class name: .dygraphDefaultAnnotation */
|
||||
.dygraph-default-annotation {
|
||||
border: 1px solid black;
|
||||
background-color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dygraph-axis-label {
|
||||
/* position: absolute; */
|
||||
/* font-size: 14px; */
|
||||
z-index: 10;
|
||||
line-height: normal;
|
||||
overflow: hidden;
|
||||
color: black; /* replaces old axisLabelColor option */
|
||||
}
|
||||
|
||||
.dygraph-axis-label-x {
|
||||
}
|
||||
|
||||
.dygraph-axis-label-y {
|
||||
}
|
||||
|
||||
.dygraph-axis-label-y2 {
|
||||
}
|
||||
|
||||
.dygraph-title {
|
||||
font-weight: bold;
|
||||
z-index: 10;
|
||||
text-align: center;
|
||||
/* font-size: based on titleHeight option */
|
||||
}
|
||||
|
||||
.dygraph-xlabel {
|
||||
text-align: center;
|
||||
/* font-size: based on xLabelHeight option */
|
||||
}
|
||||
|
||||
/* For y-axis label */
|
||||
.dygraph-label-rotate-left {
|
||||
text-align: center;
|
||||
/* See http://caniuse.com/#feat=transforms2d */
|
||||
transform: rotate(90deg);
|
||||
-webkit-transform: rotate(90deg);
|
||||
-moz-transform: rotate(90deg);
|
||||
-o-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* For y2-axis label */
|
||||
.dygraph-label-rotate-right {
|
||||
text-align: center;
|
||||
/* See http://caniuse.com/#feat=transforms2d */
|
||||
transform: rotate(-90deg);
|
||||
-webkit-transform: rotate(-90deg);
|
||||
-moz-transform: rotate(-90deg);
|
||||
-o-transform: rotate(-90deg);
|
||||
-ms-transform: rotate(-90deg);
|
||||
}
|
||||
BIN
web/static/img/53096.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
52
web/static/img/53096.svg
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="938px" height="938px" viewBox="0 0 938 938" style="enable-background:new 0 0 938 938;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M50.792,747.965c0,13.415,10.875,24.29,24.29,24.29h787.836c13.415,0,24.29-10.875,24.29-24.29v-408.36H50.792V747.965z
|
||||
M649.792,476.871c0-6.419,5.203-11.622,11.622-11.622h112.098c6.419,0,11.622,5.203,11.622,11.622v27.54
|
||||
c0,6.419-5.203,11.622-11.622,11.622H661.414c-6.419,0-11.622-5.203-11.622-11.622V476.871z M152.866,476.295
|
||||
c0-6.091,4.938-11.029,11.029-11.029h36.764v-36.8c0-6.091,4.938-11.028,11.029-11.028h28.725c6.091,0,11.029,4.938,11.029,11.028
|
||||
v36.8h36.764c6.091,0,11.029,4.938,11.029,11.029v28.727c0,6.091-4.938,11.028-11.029,11.028h-36.764v36.765
|
||||
c0,6.091-4.938,11.028-11.029,11.028h-28.725c-6.091,0-11.029-4.938-11.029-11.028V516.05h-36.764
|
||||
c-6.091,0-11.029-4.938-11.029-11.028V476.295L152.866,476.295z"/>
|
||||
<path d="M915.285,314.605c12.545,0,22.715-10.17,22.715-22.715v-38.164c0-12.545-10.17-22.715-22.715-22.715H788.537v-42.552
|
||||
c0-12.545-10.17-22.715-22.715-22.715h-97.83c-12.545,0-22.715,10.17-22.715,22.715v42.552H301.611v-42.552
|
||||
c0-12.545-10.17-22.715-22.715-22.715h-97.83c-12.545,0-22.715,10.17-22.715,22.715v42.552H22.715
|
||||
C10.17,231.01,0,241.181,0,253.727v38.165c0,12.545,10.17,22.715,22.715,22.715h28.076h836.417L915.285,314.605L915.285,314.605z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
BIN
web/static/img/53096_s.jpg
Normal file
|
After Width: | Height: | Size: 921 B |
BIN
web/static/img/battery_0.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
web/static/img/battery_20.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
web/static/img/battery_40.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
web/static/img/battery_60.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
web/static/img/battery_80.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
web/static/img/battery_charging.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
web/static/img/battery_current.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
web/static/img/battery_full.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
web/static/img/battery_power.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
web/static/img/humidity_icon.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
web/static/img/inside_temp_icon.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
web/static/img/outside_temp_icon.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
web/static/img/pressure_icon.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
web/static/img/temperature_icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
web/static/img/wind_direction_icon.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
web/static/img/wind_gust_icon.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
web/static/img/wind_icon.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
3535
web/static/js/bootstrap.js
vendored
Normal file
7
web/static/js/bootstrap.min.js
vendored
Normal file
9383
web/static/js/dygraph.js
Normal file
24
web/static/js/ie10bugfix.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* IE10 viewport hack for Surface/desktop Windows 8 bug
|
||||
* Copyright 2014-2017 The Bootstrap Authors
|
||||
* Copyright 2014-2017 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
// See the Getting Started docs for more information:
|
||||
// https://getbootstrap.com/getting-started/#support-ie10-width
|
||||
|
||||
(function () {
|
||||
'use strict'
|
||||
|
||||
if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
|
||||
var msViewportStyle = document.createElement('style')
|
||||
msViewportStyle.appendChild(
|
||||
document.createTextNode(
|
||||
'@-ms-viewport{width:auto!important}'
|
||||
)
|
||||
)
|
||||
document.head.appendChild(msViewportStyle)
|
||||
}
|
||||
|
||||
}())
|
||||
120
web/static/js/solar_graph.js
Normal file
@@ -0,0 +1,120 @@
|
||||
var hours = 24;
|
||||
var granularity = '2m';
|
||||
var end = 0;
|
||||
var graphdata = "https://bastart.spoton.cz/data/solar_monitor?range=24h&granularity=2m&end=0h";
|
||||
sol = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("solar"),
|
||||
// CSV or path to a CSV file.
|
||||
graphdata
|
||||
,{
|
||||
//labels: ['time','V_solar','Isolar', P_solar, P_cons],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : true
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
independentTicks: true
|
||||
}
|
||||
},
|
||||
rollPeriod: 5,
|
||||
visibility: [true, false, true, true],
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
series:{
|
||||
'V_solar': {
|
||||
axis: 'y',
|
||||
color: '#ffd020',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.4
|
||||
},
|
||||
'I_solar': {
|
||||
axis: 'y',
|
||||
color: '#ff1100'
|
||||
},
|
||||
'P_solar': {
|
||||
axis: 'y2',
|
||||
color: '#1111ff',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.4
|
||||
},
|
||||
'P_cons': {
|
||||
axis: 'y2',
|
||||
color: '#ff1111',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.4
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#ffd020;">[V]</span>/<span style="color:#ff1100;">[A]</span>',
|
||||
y2label: '<span style="color:#111177;">Solar / Consumption [W]</span>',
|
||||
labelsDiv: 'solar_labels',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
function refreshGraph(){
|
||||
graphdata = "https://bastart.spoton.cz/data/solar_monitor?range=" + hours + "h&granularity=" + granularity + "&end=" + end + "h";
|
||||
sol.updateOptions({'file': graphdata});
|
||||
//power.updateOptions({'file': graphdata});
|
||||
}
|
||||
|
||||
function setHours(hours_to_set){
|
||||
hours = hours_to_set;
|
||||
switch(hours){
|
||||
case '1':
|
||||
granularity = '30s';
|
||||
break;
|
||||
case '6':
|
||||
granularity = '1m';
|
||||
break;
|
||||
case '12':
|
||||
granularity = '1m';
|
||||
break;
|
||||
case '24':
|
||||
granularity = '2m';
|
||||
break;
|
||||
case '168':
|
||||
granularity = '20m';
|
||||
break;
|
||||
case '720':
|
||||
granularity = '1h';
|
||||
break;
|
||||
default:
|
||||
granularity = '10m';
|
||||
}
|
||||
end = 0;
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph();
|
||||
}
|
||||
|
||||
function setBack(){
|
||||
// range=1h -> range=2h&end=1h
|
||||
disp_range = hours*1 - end*1;
|
||||
hours = hours*1 + disp_range;
|
||||
end = end*1 + disp_range;
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph();
|
||||
}
|
||||
|
||||
function setForth(){
|
||||
disp_range = hours*1 - end*1;
|
||||
hours = hours*1 - disp_range;
|
||||
if(hours < disp_range){
|
||||
hours = disp_range;
|
||||
}
|
||||
end = end*1 - disp_range;
|
||||
if(end < 0){
|
||||
end = 0;
|
||||
}
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph();
|
||||
}
|
||||
174
web/static/js/solcap_graph.js
Normal file
@@ -0,0 +1,174 @@
|
||||
var hours = 24;
|
||||
var granularity = '2m';
|
||||
var end = 0;
|
||||
var solcapdata = "https://bastart.spoton.cz/data/solcap_monitor?range=24h&granularity=2m&end=0h";
|
||||
var cpumemdata = "https://bastart.spoton.cz/data/cpumem_monitor?range=24h&granularity=2m&end=0h";
|
||||
|
||||
solcap = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("solcap"),
|
||||
// CSV or path to a CSV file.
|
||||
solcapdata
|
||||
,{
|
||||
//labels: ['time','Solar','Capacitor'],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : true
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true,
|
||||
valueRange: [0,4.6]
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
valueRange: [0,8.6],
|
||||
independentTicks: true
|
||||
}
|
||||
},
|
||||
rollPeriod: 5,
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
series:{
|
||||
'Solar': {
|
||||
axis: 'y',
|
||||
color: '#888844',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.4
|
||||
},
|
||||
'Capacitor': {
|
||||
axis: 'y2',
|
||||
color: '#884444'
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#888844;">Solar Irradiance</span>',
|
||||
y2label: '<span style="color:#884444;">Capacitor [V]</span>',
|
||||
labelsDiv: 'solcap_labels',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
cpumem = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("cpumem"),
|
||||
// CSV or path to a CSV file.
|
||||
cpumemdata
|
||||
,{
|
||||
//labels: ['time','cpu','mem','disk'],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : true
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true,
|
||||
valueRange: [0,100]
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
valueRange: [0,100],
|
||||
independentTicks: true
|
||||
}
|
||||
},
|
||||
rollPeriod: 5,
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
series:{
|
||||
'Cpu': {
|
||||
axis: 'y',
|
||||
color: '#888844',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.4
|
||||
},
|
||||
'Mem': {
|
||||
axis: 'y2',
|
||||
color: '#884444'
|
||||
},
|
||||
'Disk': {
|
||||
axis: 'y2',
|
||||
color: '#448844'
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#888844;">CPU [%]</span>',
|
||||
y2label: '<span style="color:#884444;">MEM / DISK [%]</span>',
|
||||
labelsDiv: 'cpumem_labels',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
function refreshGraph(source){
|
||||
graphdata = "https://bastart.spoton.cz/data/" + source + "?range=" + hours + "h&granularity=" + granularity + "&end=" + end + "h";
|
||||
if(source == 'solcap_monitor'){
|
||||
solcap.updateOptions({'file': graphdata});
|
||||
}
|
||||
if(source == 'cpumem_monitor'){
|
||||
cpumem.updateOptions({'file': graphdata});
|
||||
}
|
||||
//document.getElementById('xxx').innerHTML = source;
|
||||
//power.updateOptions({'file': graphdata});
|
||||
}
|
||||
|
||||
function setHours(hours_to_set, target){
|
||||
hours = hours_to_set;
|
||||
switch(hours){
|
||||
case '1':
|
||||
granularity = '10s';
|
||||
break;
|
||||
case '6':
|
||||
granularity = '2m';
|
||||
break;
|
||||
case '12':
|
||||
granularity = '2m';
|
||||
break;
|
||||
case '24':
|
||||
granularity = '5m';
|
||||
break;
|
||||
case '168':
|
||||
granularity = '10m';
|
||||
break;
|
||||
case '720':
|
||||
granularity = '1h';
|
||||
break;
|
||||
case '4320':
|
||||
granularity = '6h';
|
||||
break;
|
||||
case '8640':
|
||||
granularity = '1d';
|
||||
break;
|
||||
default:
|
||||
granularity = '10m';
|
||||
}
|
||||
end = 0;
|
||||
//document.getElementById('xxx').innerHTML = target;
|
||||
refreshGraph(target);
|
||||
}
|
||||
|
||||
function setBack(target){
|
||||
// range=1h -> range=2h&end=1h
|
||||
disp_range = hours*1 - end*1;
|
||||
hours = hours*1 + disp_range;
|
||||
end = end*1 + disp_range;
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph(target);
|
||||
}
|
||||
|
||||
function setForth(target){
|
||||
disp_range = hours*1 - end*1;
|
||||
hours = hours*1 - disp_range;
|
||||
if(hours < disp_range){
|
||||
hours = disp_range;
|
||||
}
|
||||
end = end*1 - disp_range;
|
||||
if(end < 0){
|
||||
end = 0;
|
||||
}
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph(target);
|
||||
}
|
||||
296
web/static/js/weather_graph.js
Normal file
@@ -0,0 +1,296 @@
|
||||
var hours = 24;
|
||||
var granularity = '1m';
|
||||
var end = 0;
|
||||
var winddata = "https://bastart.spoton.cz/data/wind_monitor?range=24h&granularity=1m&end=0h";
|
||||
var temphumidata = "https://bastart.spoton.cz/data/temphumi_monitor?range=24h&granularity=2m&end=0h";
|
||||
var pressuredata = "https://bastart.spoton.cz/data/pressure_monitor?range=168h&granularity=2m&end=0h";
|
||||
wind = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("wind"),
|
||||
// CSV or path to a CSV file.
|
||||
winddata
|
||||
,{
|
||||
//labels: ['time','Speed','Gust', 'Direction'],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : true
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true,
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
valueRange: [0,360],
|
||||
independentTicks: true
|
||||
}
|
||||
},
|
||||
rollPeriod: 20,
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
series:{
|
||||
'Speed': {
|
||||
axis: 'y',
|
||||
color: '#444444',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.4
|
||||
},
|
||||
'Gusts': {
|
||||
axis: 'y',
|
||||
color: '#ff5555'
|
||||
},
|
||||
'Direction': {
|
||||
axis: 'y2',
|
||||
color: '#999999'
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#444444;">Speed<span style="color:#444444;"> / <span style="color:#ff5555;">Gusts</span> [km/h]',
|
||||
y2label: '<span style="color:#999999;">Direction [°]</span>',
|
||||
labelsDiv: 'wind_labels',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
temphumi_out = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("temphumi"),
|
||||
// CSV or path to a CSV file.
|
||||
temphumidata
|
||||
,{
|
||||
//labels: [time,T(ins),T(out),Humi(ins),Humi(out)],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : false
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true,
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
independentTicks: true
|
||||
}
|
||||
},
|
||||
rollPeriod: 10,
|
||||
visibility: [false, true, false, true],
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
series:{
|
||||
'T(out)': {
|
||||
axis: 'y',
|
||||
color: '#705555',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.2
|
||||
},
|
||||
'T(ins)': {
|
||||
axis: 'y',
|
||||
color: '#705555',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.2
|
||||
},
|
||||
'Humi(out)': {
|
||||
axis: 'y2',
|
||||
color: '#222288'
|
||||
},
|
||||
'Humi(ins)': {
|
||||
axis: 'y2',
|
||||
color: '#222288'
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#555555;">Outside [°C]</span>',
|
||||
y2label: '<span style="color:#222288;">Humidity [%]</span>',
|
||||
labelsDiv: 'temphumi_labels',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
temphumi_in = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("temphumi_in"),
|
||||
// CSV or path to a CSV file.
|
||||
temphumidata
|
||||
,{
|
||||
//labels: [time,T(ins),T(out),Humi(ins),Humi(out)],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : true
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true,
|
||||
valueRange: [10,35]
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
independentTicks: true,
|
||||
valueRange: [0,100]
|
||||
}
|
||||
},
|
||||
rollPeriod: 10,
|
||||
visibility: [true, false, true, false],
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
series:{
|
||||
'T(out)': {
|
||||
axis: 'y',
|
||||
color: '#705555',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.2
|
||||
},
|
||||
'T(ins)': {
|
||||
axis: 'y',
|
||||
color: '#705555',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.2
|
||||
},
|
||||
'Humi(out)': {
|
||||
axis: 'y2',
|
||||
color: '#222288'
|
||||
},
|
||||
'Humi(ins)': {
|
||||
axis: 'y2',
|
||||
color: '#222288'
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#555555;">Inside [°C]</span>',
|
||||
y2label: '<span style="color:#222288;">Humidity [%]</span>',
|
||||
labelsDiv: 'temphumi_labels_in',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
pressure = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("pressure"),
|
||||
// CSV or path to a CSV file.
|
||||
pressuredata
|
||||
,{
|
||||
//labels: [time,Pressure],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : true
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true,
|
||||
valueRange: [970,1055]
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
valueRange: [970,1055]
|
||||
}
|
||||
},
|
||||
rollPeriod: 10,
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
visibility: [true, false],
|
||||
series:{
|
||||
'Pressure': {
|
||||
axis: 'y',
|
||||
color: '#557070',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.5
|
||||
},
|
||||
'Pressure': {
|
||||
axis: 'y2',
|
||||
color: '#557070',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.5
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#555555;">Pressure [hPa]</span>',
|
||||
labelsDiv: 'pressure_labels',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
function refreshGraph(source){
|
||||
graphdata = "https://bastart.spoton.cz/data/" + source + "?range=" + hours + "h&granularity=" + granularity + "&end=" + end + "h";
|
||||
if(source == 'wind_monitor'){
|
||||
wind.updateOptions({'file': graphdata});
|
||||
}
|
||||
if(source == 'temphumi_monitor'){
|
||||
temphumi_out.updateOptions({'file': graphdata});
|
||||
temphumi_in.updateOptions({'file': graphdata});
|
||||
}
|
||||
if(source == 'pressure_monitor'){
|
||||
pressure.updateOptions({'file': graphdata});
|
||||
}
|
||||
//document.getElementById('xxx').innerHTML = source;
|
||||
//power.updateOptions({'file': graphdata});
|
||||
}
|
||||
|
||||
function setHours(hours_to_set, target){
|
||||
hours = hours_to_set;
|
||||
switch(hours){
|
||||
case '1':
|
||||
granularity = '1m';
|
||||
if(target == 'temphumi_monitor'){ granularity = '2m';}
|
||||
break;
|
||||
case '6':
|
||||
granularity = '1m';
|
||||
if(target == 'temphumi_monitor'){ granularity = '2m';}
|
||||
break;
|
||||
case '12':
|
||||
granularity = '1m';
|
||||
if(target == 'temphumi_monitor'){ granularity = '2m';}
|
||||
break;
|
||||
case '24':
|
||||
granularity = '1m';
|
||||
if(target == 'temphumi_monitor'){ granularity = '2m';}
|
||||
break;
|
||||
case '168':
|
||||
granularity = '10m';
|
||||
break;
|
||||
case '720':
|
||||
granularity = '1h';
|
||||
break;
|
||||
case '4320':
|
||||
granularity = '6h';
|
||||
break;
|
||||
case '8640':
|
||||
granularity = '1d';
|
||||
break;
|
||||
default:
|
||||
granularity = '10m';
|
||||
}
|
||||
end = 0;
|
||||
//document.getElementById('xxx').innerHTML = target;
|
||||
refreshGraph(target);
|
||||
}
|
||||
|
||||
function setBack(target){
|
||||
// range=1h -> range=2h&end=1h
|
||||
disp_range = hours*1 - end*1;
|
||||
hours = hours*1 + disp_range;
|
||||
end = end*1 + disp_range;
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph(target);
|
||||
}
|
||||
|
||||
function setForth(target){
|
||||
disp_range = hours*1 - end*1;
|
||||
hours = hours*1 - disp_range;
|
||||
if(hours < disp_range){
|
||||
hours = disp_range;
|
||||
}
|
||||
end = end*1 - disp_range;
|
||||
if(end < 0){
|
||||
end = 0;
|
||||
}
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph(target);
|
||||
}
|
||||
106
web/status.py
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import cherrypy
|
||||
import influxdb
|
||||
import time
|
||||
import json
|
||||
|
||||
# Universal variables
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
_STATIC_DIR = '/templates/' # Needs to have trailing and leading slash '/'
|
||||
|
||||
influx_host = 'localhost'
|
||||
influx_port = 8086
|
||||
influx_user = 'pi'
|
||||
influx_pwd = 'freedavis'
|
||||
influx_status_db = 'status'
|
||||
|
||||
# Icons
|
||||
fs_sol_icon = '../static/img/iss_solar_icon.png'
|
||||
fs_cap_icon = '../static/img/iss_capacitor_icon.png'
|
||||
|
||||
# Functions
|
||||
def read_html(filename):
|
||||
read_path = _SCRIPT_PATH + _STATIC_DIR + filename + '.html'
|
||||
try:
|
||||
with open(read_path, 'r') as handle:
|
||||
return handle.read()
|
||||
except:
|
||||
return """<div>ERROR!</div>""" + read_path
|
||||
|
||||
# Set default structure
|
||||
|
||||
html_start = '<html>'
|
||||
body_start = '<body>'
|
||||
body_close = '</body>'
|
||||
html_close = '</html>'
|
||||
|
||||
class StatusInfo(object):
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
header = read_html('header')
|
||||
menu_raw = read_html('top_menu')
|
||||
menu = menu_raw.format(energy = '', weather = '', status = 'active')
|
||||
body = self.body()
|
||||
footer = read_html('footer')
|
||||
result = header\
|
||||
+ menu\
|
||||
+ body\
|
||||
+ footer
|
||||
return result
|
||||
|
||||
def LastKnownState(self):
|
||||
'''
|
||||
Function to get energy readings from InfluxDB.
|
||||
|
||||
returns:
|
||||
dict(): {"time": str(time_stamp),
|
||||
"solar": solar,
|
||||
"cap": cap}
|
||||
'''
|
||||
|
||||
influx_status_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_status_db
|
||||
)
|
||||
|
||||
# General query
|
||||
query1 = "SELECT time, voltage FROM iss WHERE type = "
|
||||
query2 = "ORDER BY time DESC LIMIT 1"
|
||||
|
||||
# now parse the tag in the middle of the two q from above
|
||||
solar_q = "{} '{}' {}".format(query1, "solar", query2)
|
||||
cap_q = "{} '{}' {}".format(query1, "capcaitor", query2)
|
||||
|
||||
# the actual query to DB
|
||||
solar = influx_status_client.query(solar_q)
|
||||
cap = influx_status_client.query(cap_q)
|
||||
|
||||
# returned is a list, in this case, we just need one value [0]
|
||||
result_solar = [sol for sol in solar][0][0]
|
||||
result_cap = [cap_ for cap_ in cap][0][0]
|
||||
|
||||
# Put the time to a uhman readable format, strip nanosecs
|
||||
time_stamp = time.strptime(result_solar['time'].split('.')[0],
|
||||
"%Y-%m-%dT%H:%M:%S")
|
||||
result = {}
|
||||
|
||||
# Construct the result to return
|
||||
result.update({"time": time_stamp})
|
||||
result.update({"solar": round(result_solar['voltage'], 1)})
|
||||
result.update({"cap": round(result_cap['voltage'], 1)})
|
||||
return result
|
||||
|
||||
|
||||
def body(self):
|
||||
admin_preformat = read_html('status_admin')
|
||||
current_status = self.LastKnownState()
|
||||
admin_html = admin_preformat.format(
|
||||
timestamp = time.strftime("%d.%m.%Y %H:%M:%S",
|
||||
current_status['time']),
|
||||
sol_icon = fs_sol_icon,
|
||||
cap_icon = fs_cap_icon,
|
||||
sol_value = round(current_status['solar'] / 100, 2),
|
||||
cap_value = current_status['cap']
|
||||
)
|
||||
return admin_html
|
||||
7
web/templates/footer.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div id="footer">
|
||||
Created in 101 minutes, in VI, while sitting in a train Wien -> Bratislava.
|
||||
Uploeaded over EDGE with a lot of signal-less territory on roaming. Cheers.
|
||||
Done by Ventil.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
23
web/templates/header.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Energy reporter</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta http-equiv="Content-Language" content="en">
|
||||
<meta name="Robots" content="all">
|
||||
<meta name="Keywords" content="automation, python, development, arduino, measurement">
|
||||
<meta name="description" content="RasPi automation hardcore">
|
||||
<meta name="Author" content="Spoton s.r.o. | Milan Toman">
|
||||
<link rel="stylesheet" type="text/css" href="./static/css/def_style.css">
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="../static/css/dashboard.css" rel="stylesheet">
|
||||
<!-- Dygraph css and js -->
|
||||
<link href="../static/css/dygraph.css" rel="stylesheet">
|
||||
<script type="text/javascript" src="../static/js/dygraph.js"></script>
|
||||
<link rel="icon" href="../../favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
65
web/templates/status_admin.html
Normal file
@@ -0,0 +1,65 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
<div class="col"></div>
|
||||
<main class="pt-4 col-10">
|
||||
<h1>Status Dashboard</h1>
|
||||
<h5>{timestamp} UTC</h5>
|
||||
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col placeholder">
|
||||
<img src="{sol_icon}" width="20" class="img-fluid" alt="ISS solar irradiation">
|
||||
<h3>{sol_value}</h3>
|
||||
<div class="text-muted">[V]</div>
|
||||
</div>
|
||||
<div class="col placeholder">
|
||||
<img src="{cap_icon}" width="20" class="img-fluid" alt="ISS Capacitor state">
|
||||
<h3>{cap_value}</h3>
|
||||
<div class="text-muted">[V]</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<div id="solcap_labels"></div>
|
||||
<div id="solcap" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('solcap_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6','solcap_monitor')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','solcap_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','solcap_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','solcap_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('720','solcap_monitor')">Month</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('4320','solcap_monitor')">6Mo</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('8640','solcap_monitor')">Year</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('solcap_monitor')">>></button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<div id="cpumem_labels"></div>
|
||||
<div id="cpumem" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('cpumem_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('1','cpumem_monitor')">1h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6','cpumem_monitor')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','cpumem_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','cpumem_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','cpumem_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('720','cpumem_monitor')">Month</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('4320','cpumem_monitor')">6Mo</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('8640','cpumem_monitor')">Year</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('cpumem_monitor')">>></button>
|
||||
</div>
|
||||
<script type="text/javascript" src="../static/js/solcap_graph.js"></script>
|
||||
</section>
|
||||
</main>
|
||||
<div class="col"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
|
||||
<script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
|
||||
<script src="../static/js/bootstrap.min.js"></script>
|
||||
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
|
||||
<script src="../static/js/ie10bugfix.js"></script>
|
||||
20
web/templates/top_menu.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<nav class="navbar navbar-toggleable-md navbar-inverse fixed-top bg-inverse">
|
||||
<button class="navbar-toggler navbar-toggler-right hidden-lg-up" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#">Monitoring</a>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item {energy}">
|
||||
<a class="nav-link" href="https://bastart.spoton.cz">Energy</a>
|
||||
</li>
|
||||
<li class="nav-item {weather}">
|
||||
<a class="nav-link" href="https://bastart.spoton.cz/weather">Weather</a>
|
||||
</li>
|
||||
<li class="nav-item {status}">
|
||||
<a class="nav-link" href="https://bastart.spoton.cz/status">Status</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
74
web/templates/voltage_admin.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
<div class="col"></div>
|
||||
<main class="pt-4 col-10">
|
||||
<h1>Energy Dashboard</h1>
|
||||
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col placeholder">
|
||||
<img src="{array_voltage_icon}" width="20" class="img-fluid" alt="Battery voltage">
|
||||
<h3>{array_voltage} [V]</h3>
|
||||
<div class="text-muted">{array_percent} [%]</div>
|
||||
</div>
|
||||
<div class="col placeholder">
|
||||
<img src="../static/img/battery_current.png" width="20" class="img-fluid" alt="Battery current">
|
||||
<h3>{charge_current}</h3>
|
||||
<div class="text-muted">Charging [A]</div>
|
||||
</div>
|
||||
<div class="col placeholder">
|
||||
<img src="../static/img/solar_power.png" width="20" class="img-fluid" alt="Solar array power">
|
||||
<h3>{solar_power}</h3>
|
||||
<span class="text-muted"> Solar PV [W]</span>
|
||||
</div>
|
||||
<div class="col placeholder">
|
||||
<img src="../static/img/solar_voltage.png" width="20" class="img-fluid" alt="Solar array voltage">
|
||||
<h3>{voltage_solar}</h3>
|
||||
<div class="text-muted">Solar [V]</div>
|
||||
</div>
|
||||
<div class="col placeholder">
|
||||
<img src="../static/img/pmax_day.png" width="20" class="img-fluid" alt="Max power / day">
|
||||
<h3>{pmax_day}</h3>
|
||||
<div class="text-muted">Pmax / day [W]</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<div id="solar_labels"></div>
|
||||
<div id="solar" class="container-fluid" style="width:100%;"></div>
|
||||
<div id="power_labels"></div>
|
||||
<div id="power" class="container-fluid" style="width:100%;"></div>
|
||||
<script type="text/javascript" src="../static/js/solar_graph.js"></script>
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack()"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('720')">Month</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth()">>></button>
|
||||
</div>
|
||||
<div id="xxx"></div>
|
||||
</section>
|
||||
|
||||
<div class="table-responisve">
|
||||
<h2>Historical values by day</h2>
|
||||
{history_table}
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
{stats_table}
|
||||
{fresh_table}
|
||||
|
||||
</div>
|
||||
</main>
|
||||
<div class="col"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
|
||||
<script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
|
||||
<script src="../static/js/bootstrap.min.js"></script>
|
||||
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
|
||||
<script src="../static/js/ie10bugfix.js"></script>
|
||||
108
web/templates/weather_admin.html
Normal file
@@ -0,0 +1,108 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
<div class="col"></div>
|
||||
<main class="pt-4 col-10">
|
||||
<h1>Weather Dashboard</h1>
|
||||
<h5>{timestamp} UTC</h5>
|
||||
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{w_speed_icon}" width="50" class="img-fluid" alt="Wind Speed">
|
||||
<h4>{w_speed_km} [km/h]</h4>
|
||||
<div class="text-muted">{w_speed_ms} [m/s]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{w_gust_icon}" width="50" class="img-fluid" alt="Wind Gusts">
|
||||
<h4>{w_gust_km} [km/h]</h4>
|
||||
<div class="text-muted">{w_gust_ms} [m/s]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{w_dir_icon}" width="50" class="img-fluid" alt="Wind Direction">
|
||||
<h4>{w_dir_name}</h4>
|
||||
<div class="text-muted">{w_dir_deg} °</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{out_temp_icon}" width="50" class="img-fluid" alt="Outside Temperature">
|
||||
<h4>{out_temp} °C</h4>
|
||||
<div class="text-muted">[Outside]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{in_temp_icon}" width="50" class="img-fluid" alt="Inside Temperature">
|
||||
<h4>{in_temp} °C</h4>
|
||||
<div class="text-muted">[Inside]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{pressure_icon}" width="50" class="img-fluid" alt="Atmospheric pressure">
|
||||
<h4>{pressure} [hPa]</h4>
|
||||
<div class="text-muted">{raw_pressure} [hPa] raw</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="row text-center">
|
||||
<div class="col">
|
||||
<h2>Wind speed and direction</h2>
|
||||
<div id="wind_labels"></div>
|
||||
<div id="wind" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<div class="row"><div class="col py-5">
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('wind_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6','wind_monitor')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','wind_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','wind_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','wind_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('720','wind_monitor')">Month</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('4320','wind_monitor')">6Mo</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('8640','wind_monitor')">Year</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('wind_monitor')">>></button>
|
||||
</div></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<h2>Temperature and Humidity</h2>
|
||||
<div id="temphumi_labels"></div><div id="temphumi_labels_in"></div>
|
||||
<div id="temphumi" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<div id="temphumi_in" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('temphumi_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6','temphumi_monitor')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','temphumi_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','temphumi_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','temphumi_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('720','temphumi_monitor')">Month</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('4320','temphumi_monitor')">6Mo</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('8640','temphumi_monitor')">Year</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('temphumi_monitor')">>></button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<div id="pressure_labels"></div>
|
||||
<div id="pressure" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('pressure_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','pressure_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','pressure_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','pressure_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('720','pressure_monitor')">Month</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('4320','pressure_monitor')">6Mo</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('8640','pressure_monitor')">Year</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('pressure_monitor')">>></button>
|
||||
</div>
|
||||
<script type="text/javascript" src="../static/js/weather_graph.js"></script>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
<div class="col" id="xxx"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
|
||||
<script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
|
||||
<script src="../static/js/bootstrap.min.js"></script>
|
||||
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
|
||||
<script src="../static/js/ie10bugfix.js"></script>
|
||||
280
web/templates/weather_admin.html.orig
Normal file
@@ -0,0 +1,280 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
<!--
|
||||
--------------------------- Nahodou menu na levo -----------------------
|
||||
--
|
||||
<nav class="col-sm-3 col-md-2 hidden-xs-down bg-faded sidebar">
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#">Overview <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Reports</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Analytics</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Export</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Nav item</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Nav item again</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">One more nav</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Another nav item</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Nav item again</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">One more nav</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Another nav item</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
-->
|
||||
<div class="col"></div>
|
||||
<main class="pt-4 col-10">
|
||||
<h1>Weather Dashboard</h1>
|
||||
<h5>{timestamp}</h5>
|
||||
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{w_speed_icon}" width="50" class="img-fluid" alt="Wind Speed">
|
||||
<h4>{w_speed_km} [km/h]</h4>
|
||||
<div class="text-muted">{w_speed_ms} [m/s]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{w_gust_icon}" width="50" class="img-fluid" alt="Wind Gusts">
|
||||
<h4>{w_gust_km} [km/h]</h4>
|
||||
<div class="text-muted">{w_gust_ms} [m/s]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{w_dir_icon}" width="50" class="img-fluid" alt="Wind Direction">
|
||||
<h4>{w_dir_name}</h4>
|
||||
<div class="text-muted">{w_dir_deg} °</div>
|
||||
</section>
|
||||
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{out_temp_icon}" width="50" class="img-fluid" alt="Outside Temperature">
|
||||
<h4>{out_temp} °C</h4>
|
||||
<div class="text-muted">[Outside]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{in_temp_icon}" width="50" class="img-fluid" alt="Inside Temperature">
|
||||
<h4>{in_temp} °C</h4>
|
||||
<div class="text-muted">[Inside]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{pressure_icon}" width="50" class="img-fluid" alt="Atmospheric pressure">
|
||||
<h4>{pressure} [hPa] Adjusted</h4>
|
||||
<div class="text-muted">{raw_pressure} [hPa] Actual</div>
|
||||
</section>
|
||||
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<h2>Wind speed and direction</h2>
|
||||
<div id="wind_labels"></div>
|
||||
<div id="wind" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<div class="row"><div class="col py-5">
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('wind_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('1','wind_monitor')">1h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6','wind_monitor')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','wind_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','wind_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','wind_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('5040','wind_monitor')">Month</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('wind_monitor')">>></button>
|
||||
</div></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<h2>Temperature and Humidity</h2>
|
||||
<div id="temphumi_labels"></div><div id="temphumi_labels_in"></div>
|
||||
<div id="temphumi" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<div id="temphumi_in" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('temphumi_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('1','temphumi_monitor')">1h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6','temphumi_monitor')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','temphumi_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','temphumi_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','temphumi_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('5040','temphumi_monitor')">Month</button>
|
||||
<<<<<<< HEAD
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('temphumi_monitor');">>></button>
|
||||
=======
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('temphumi_monitor')">>></button>
|
||||
>>>>>>> ca62f04757f1010262e5559bbd8753bfa49bf34e
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<div id="pressure_labels"></div>
|
||||
<div id="pressure" class="container-fluid" style="width:100%;"></div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../static/js/weather_graph.js"></script>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
<div class="col" id="xxx"></div>
|
||||
|
||||
<!--
|
||||
<h2>Section title</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Header</th>
|
||||
<th>Header</th>
|
||||
<th>Header</th>
|
||||
<th>Header</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1,001</td>
|
||||
<td>Lorem</td>
|
||||
<td>ipsum</td>
|
||||
<td>dolor</td>
|
||||
<td>sit</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,002</td>
|
||||
<td>amet</td>
|
||||
<td>consectetur</td>
|
||||
<td>adipiscing</td>
|
||||
<td>elit</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,003</td>
|
||||
<td>Integer</td>
|
||||
<td>nec</td>
|
||||
<td>odio</td>
|
||||
<td>Praesent</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,003</td>
|
||||
<td>libero</td>
|
||||
<td>Sed</td>
|
||||
<td>cursus</td>
|
||||
<td>ante</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,004</td>
|
||||
<td>dapibus</td>
|
||||
<td>diam</td>
|
||||
<td>Sed</td>
|
||||
<td>nisi</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,005</td>
|
||||
<td>Nulla</td>
|
||||
<td>quis</td>
|
||||
<td>sem</td>
|
||||
<td>at</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,006</td>
|
||||
<td>nibh</td>
|
||||
<td>elementum</td>
|
||||
<td>imperdiet</td>
|
||||
<td>Duis</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,007</td>
|
||||
<td>sagittis</td>
|
||||
<td>ipsum</td>
|
||||
<td>Praesent</td>
|
||||
<td>mauris</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,008</td>
|
||||
<td>Fusce</td>
|
||||
<td>nec</td>
|
||||
<td>tellus</td>
|
||||
<td>sed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,009</td>
|
||||
<td>augue</td>
|
||||
<td>semper</td>
|
||||
<td>porta</td>
|
||||
<td>Mauris</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,010</td>
|
||||
<td>massa</td>
|
||||
<td>Vestibulum</td>
|
||||
<td>lacinia</td>
|
||||
<td>arcu</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,011</td>
|
||||
<td>eget</td>
|
||||
<td>nulla</td>
|
||||
<td>Class</td>
|
||||
<td>aptent</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,012</td>
|
||||
<td>taciti</td>
|
||||
<td>sociosqu</td>
|
||||
<td>ad</td>
|
||||
<td>litora</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,013</td>
|
||||
<td>torquent</td>
|
||||
<td>per</td>
|
||||
<td>conubia</td>
|
||||
<td>nostra</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,014</td>
|
||||
<td>per</td>
|
||||
<td>inceptos</td>
|
||||
<td>himenaeos</td>
|
||||
<td>Curabitur</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,015</td>
|
||||
<td>sodales</td>
|
||||
<td>ligula</td>
|
||||
<td>in</td>
|
||||
<td>libero</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
|
||||
<script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
|
||||
<script src="../static/js/bootstrap.min.js"></script>
|
||||
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
|
||||
<script src="../static/js/ie10bugfix.js"></script>
|
||||
317
web/voltage.py
Executable file
@@ -0,0 +1,317 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import cherrypy
|
||||
import influxdb
|
||||
import time
|
||||
import datetime
|
||||
import json
|
||||
from dynamic import DynamicData
|
||||
|
||||
# Universal variables
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
_STATIC_DIR = '/templates/' # Needs to have trailing and leading slash '/'
|
||||
|
||||
influx_host = 'localhost'
|
||||
influx_port = 8086
|
||||
influx_user = 'pi'
|
||||
influx_pwd = 'freedavis'
|
||||
influx_db = 'voltage'
|
||||
|
||||
_MEASURES_IN_MINUTE = 11
|
||||
|
||||
|
||||
# Functions
|
||||
def read_html(filename):
|
||||
read_path = _SCRIPT_PATH + _STATIC_DIR + filename + '.html'
|
||||
try:
|
||||
with open(read_path, 'r') as handle:
|
||||
return handle.read()
|
||||
except:
|
||||
return """<div>ERROR!</div>""" + read_path
|
||||
|
||||
# Set default structure
|
||||
|
||||
html_start = '<html>'
|
||||
body_start = '<body>'
|
||||
body_close = '</body>'
|
||||
html_close = '</html>'
|
||||
|
||||
class EnergyInfo(object):
|
||||
def __init__(self):
|
||||
self.influx_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_db
|
||||
)
|
||||
self.array_full = '../static/img/battery_full.png' #100-90
|
||||
self.array_80 = '../static/img/battery_80.png' # 90-70
|
||||
self.array_60 = '../static/img/battery_60.png' # 70-50
|
||||
self.array_40 = '../static/img/battery_40.png' # 50-30
|
||||
self.array_20 = '../static/img/battery_20.png' # 30-10
|
||||
self.array_0 = '../static/img/battery_0.png' # 10-0
|
||||
self.array_ch = '../static/img/battery_charging.png'
|
||||
self.battery_icon = self.array_full
|
||||
self.measures_obj = DynamicData()
|
||||
|
||||
def set_battery_icon(self, percentage, state):
|
||||
''' Interprets % of battery state in icon changes
|
||||
|
||||
expects:
|
||||
float(percentage): Percentage -> battery icon
|
||||
|
||||
sets:
|
||||
str(self.battery_icon): the path to appropriate icon image
|
||||
'''
|
||||
if state == 1:
|
||||
self.battery_icon = self.array_ch
|
||||
else:
|
||||
if percentage > 70.0 and percentage < 89.9:
|
||||
self.battery_icon = self.array_80
|
||||
if percentage > 50.0 and percentage < 69.9:
|
||||
self.battery_icon = self.array_60
|
||||
if percentage > 30.0 and percentage < 49.9:
|
||||
self.battery_icon = self.array_40
|
||||
if percentage > 10.0 and percentage < 29.9:
|
||||
self.battery_icon = self.array_20
|
||||
if percentage > 0.0 and percentage < 9.9:
|
||||
self.battery_icon = self.array_0
|
||||
|
||||
def percentageCalc(self, voltage, system):
|
||||
''' Turns current charge for lead acid batteries into a human readable %
|
||||
|
||||
expects:
|
||||
float(voltage): Voltage in V
|
||||
int(system): nominal system voltage, e.g. 12, 24, 48 etc
|
||||
|
||||
returns:
|
||||
float(percentage): Two decimal state of battery in percentage
|
||||
'''
|
||||
if system is 12:
|
||||
percentage = round(24.5724168782\
|
||||
* voltage * voltage - 521.9890329784 * voltage\
|
||||
+ 2771.1828105637, 1)
|
||||
elif system is 24:
|
||||
percentage = 2.4442 * voltage * voltage\
|
||||
- 82.004 * voltage + 602.91
|
||||
percentage = 100.00 if percentage > 100.00 else percentage
|
||||
return percentage
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
header = read_html('header')
|
||||
menu_raw = read_html('top_menu')
|
||||
menu = menu_raw.format(energy = 'active', weather = '', status = '')
|
||||
body = self.body()
|
||||
footer = read_html('footer')
|
||||
result = header\
|
||||
+ menu\
|
||||
+ body\
|
||||
+ footer
|
||||
return result
|
||||
|
||||
def FreshValues(self):
|
||||
''' Get most up-to-date energy reading.
|
||||
|
||||
returns:
|
||||
dict(): {'I_solar': float(mA),
|
||||
'V_unit2': float(mV),
|
||||
'time': 'YYYY-mm-DDTHH:MM:SS.149636706Z',
|
||||
'I_array': None, # ((deprecated, will be removed))
|
||||
'V_unit1': float(mV),
|
||||
'V_solar': float(mV),
|
||||
'I_consumption': float(mA),
|
||||
'V_array': float(mV),
|
||||
'charging': bool(0/1)}
|
||||
'''
|
||||
query = "SELECT * FROM voltage ORDER BY time DESC LIMIT 1"
|
||||
last_measure = self.influx_client.query(query)
|
||||
charge_current = self.influx_client.query("select current from mppt where type = 'bat' order by time desc limit 1")
|
||||
pmax_day = self.influx_client.query("select Pmax from mppt where type = 'day' order by time desc limit 1")
|
||||
wh_day = self.influx_client.query("select Wh from mppt where type = 'day' order by time desc limit 1")
|
||||
power_solar = self.influx_client.query("select power from mppt where type = 'solar' order by time desc limit 1")
|
||||
state_r = self.influx_client.query("select * from mppt order by time desc limit 1")
|
||||
result = [measure for measure in last_measure["voltage"]][0]
|
||||
charge_current_result = [ch_curr for ch_curr in charge_current["mppt"]][0]["current"]
|
||||
pmax_day_result = [pmax for pmax in pmax_day["mppt"]][0]["Pmax"]
|
||||
wh_day_result = [wh for wh in wh_day["mppt"]][0]["Wh"]
|
||||
power_solar_result = [Psol for Psol in power_solar["mppt"]][0]["power"]
|
||||
state_result = [state for state in state_r["mppt"]][0]["state"]
|
||||
time_stamp = time.strptime(result['time'].split('.')[0],
|
||||
"%Y-%m-%dT%H:%M:%S")
|
||||
result['time'] = time_stamp
|
||||
result['state'] = state_result
|
||||
result['Psol'] = power_solar_result
|
||||
result['Wh_day'] = wh_day_result
|
||||
result['Pmax_day'] = pmax_day_result
|
||||
result['ChCurr'] = charge_current_result
|
||||
return result
|
||||
|
||||
|
||||
def HistoryValues(self, _INTERVAL):
|
||||
''' Function to get energy readings from InfluxDB.
|
||||
|
||||
returns:
|
||||
dict(): {"voltage_data": [{time: str(), V_array: str()...}, {...}],
|
||||
"voltage_data_cli": [str(line1), str(line2)...]}
|
||||
'''
|
||||
|
||||
table_list = []
|
||||
query1 = "SELECT * FROM voltage"
|
||||
query2 = "ORDER BY time DESC".format(_INTERVAL)
|
||||
query = "{} {}".format(query1, query2)
|
||||
neasured_range = {'range': '1h'}
|
||||
measures = self.measures_obj.historical_values(range = '1h')
|
||||
|
||||
# Let's get the data from DB
|
||||
for datapoint in measures:
|
||||
time_stamp = time.strptime(datapoint["time"].split('.')[0],
|
||||
"%Y-%m-%dT%H:%M:%SZ")
|
||||
array_volts = round(datapoint["V_array"] / 1000, 2)
|
||||
current_consumption = round(datapoint["I_consumption"] / 1000, 2)
|
||||
solar_volts = round(datapoint["V_solar"] / 1000, 2)
|
||||
current_solar = round(datapoint["I_solar"] / 1000, 2)
|
||||
unit1_volts = round(datapoint["V_unit1"] / 1000, 2)
|
||||
unit2_volts = round(datapoint["V_unit2"] / 1000, 2)
|
||||
charging = datapoint["charging"]
|
||||
power_consumption = round(current_consumption * 48, 2)
|
||||
power_solar = round(current_solar * solar_volts, 2)
|
||||
percent_array = self.percentageCalc(array_volts, 24)
|
||||
percent_unit1 = self.percentageCalc(unit1_volts, 12)
|
||||
percent_unit2 = self.percentageCalc(unit2_volts, 12)
|
||||
table_list.append([time.strftime("%Y/%m/%d %H:%M:%S", time_stamp),
|
||||
array_volts,
|
||||
current_consumption,
|
||||
solar_volts,
|
||||
current_solar,
|
||||
unit1_volts,
|
||||
unit2_volts,
|
||||
charging,
|
||||
power_consumption,
|
||||
power_solar])
|
||||
print(table_list)
|
||||
return {"voltage_data": table_list}
|
||||
|
||||
def tableConstructor(self, header, body):
|
||||
''' The idea behind is to have a method of constructing <table> html
|
||||
element and to separate data and html code.
|
||||
|
||||
expects:
|
||||
list(header): [[str(header1), str(header2), ...], row2, row3, ...]
|
||||
list(body): [str(td1), str(td2), ...]
|
||||
|
||||
returns:
|
||||
str(html_table)
|
||||
'''
|
||||
table_header = ''
|
||||
table_body = ''
|
||||
table_begin = '<table class="table table-striped">'
|
||||
thead = '<thead>'
|
||||
thead_end = '</thead>'
|
||||
tbody = '<tbody>'
|
||||
tbody_end = '</tbody>'
|
||||
table_end = '</table>'
|
||||
table_header = '</tr>'
|
||||
for th in header:
|
||||
table_header = table_header + '<th>' + th + '</th>'
|
||||
table_header = table_header + '</tr>'
|
||||
for tr in body:
|
||||
table_body = table_body + '<tr>'
|
||||
for td in tr:
|
||||
table_body = table_body + '<td>' + str(td) + '</td>'
|
||||
table_body = table_body + '</tr>'
|
||||
|
||||
html_table = "{} {} {} {} {}".format(table_begin + thead,
|
||||
table_header,
|
||||
thead_end + tbody,
|
||||
table_body,
|
||||
tbody_end + table_end)
|
||||
return html_table
|
||||
|
||||
def body(self):
|
||||
lastState = self.FreshValues()
|
||||
fresh_time = time.strftime("%Y-%m-%d %H:%M:%S", lastState['time'])
|
||||
try:
|
||||
fresh_voltage_array = round(float(lastState['V_array'] / 1000), 2)
|
||||
except:
|
||||
fresh_voltage_array = -99
|
||||
fresh_ch_current = lastState['ChCurr']
|
||||
try:
|
||||
fresh_voltage_solar = round(float(lastState['V_solar'] / 1000), 2)
|
||||
except:
|
||||
fresh_voltage_solar = -99
|
||||
fresh_current_solar = 10 #round(float(lastState['I_solar'] / 1000), 2)
|
||||
try:
|
||||
fresh_pmax_day = lastState['Pmax_day']
|
||||
except:
|
||||
fresh_pmax_day = -99
|
||||
fresh_voltage_unit2 = 10 #round(float(lastState['V_unit2'] / 1000), 2)
|
||||
fresh_percent_array = self.percentageCalc(fresh_voltage_array, 24)
|
||||
fresh_percent_unit2 = self.percentageCalc(fresh_voltage_unit2, 12)
|
||||
fresh_power_consumption = round(48\
|
||||
* fresh_ch_current, 2)
|
||||
fresh_power_solar = round(lastState['Psol'])
|
||||
fresh_charge_state = lastState['charging']
|
||||
fresh_table_header = ['Date Time',
|
||||
'Uarr [V]',
|
||||
'Icons [A]',
|
||||
'Usol [V]',
|
||||
'Isol [A]',
|
||||
'Us1 [V]',
|
||||
'Us2 [V]',
|
||||
'Charge',
|
||||
'Pcons [W]',
|
||||
'Psol [W]']
|
||||
|
||||
fresh_table_contents = [[fresh_time,
|
||||
fresh_voltage_array,
|
||||
fresh_ch_current,
|
||||
fresh_voltage_solar,
|
||||
fresh_current_solar,
|
||||
fresh_voltage_unit2,
|
||||
fresh_charge_state,
|
||||
fresh_power_consumption,
|
||||
fresh_power_solar]]
|
||||
|
||||
fresh_table = self.tableConstructor(fresh_table_header,
|
||||
fresh_table_contents)
|
||||
fresh_table = ''
|
||||
|
||||
#history_table_header = fresh_table_header
|
||||
#history_table_contents = self.HistoryValues(10)["voltage_data"]
|
||||
#history_table = self.tableConstructor(history_table_header,
|
||||
# history_table_contents)
|
||||
|
||||
history_table = ''
|
||||
|
||||
# Statistical table preparation
|
||||
stat_header = ['Date',
|
||||
'Uarr min [V]',
|
||||
'Icons [Ah]',
|
||||
'Usol max [V]',
|
||||
'Isol [Ah]',
|
||||
'Us1 min [V]',
|
||||
'Us2 min [V]',
|
||||
'Charge [h]',
|
||||
'Pcons [Wh]',
|
||||
'Psol [Wh]']
|
||||
|
||||
stats_content = self.measures_obj.stat_values(days = 7)
|
||||
stats_table = self.tableConstructor(stat_header, stats_content)
|
||||
|
||||
#self.set_battery_icon(array_percent_adjusted, fresh_charge_state)
|
||||
admin_preformat = read_html('voltage_admin')
|
||||
admin_html = admin_preformat.format(array_voltage = fresh_voltage_array,
|
||||
charge_current = fresh_ch_current,
|
||||
solar_power = fresh_power_solar,
|
||||
voltage_solar = fresh_voltage_solar,
|
||||
pmax_day = fresh_pmax_day,
|
||||
array_voltage_icon = '../static/img/battery_full.png',
|
||||
array_percent = 0,
|
||||
unit1_percentage = 0,
|
||||
unit2_percentage = 0,
|
||||
fresh_table = fresh_table,
|
||||
history_table = history_table,
|
||||
stats_table = stats_table)
|
||||
|
||||
|
||||
|
||||
return admin_html
|
||||
200
web/weather.py
Executable file
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import cherrypy
|
||||
import influxdb
|
||||
import time
|
||||
import json
|
||||
|
||||
# Universal variables
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
_STATIC_DIR = '/templates/' # Needs to have trailing and leading slash '/'
|
||||
|
||||
influx_host = 'localhost'
|
||||
influx_port = 8086
|
||||
influx_user = 'pi'
|
||||
influx_pwd = 'freedavis'
|
||||
influx_weather_db = 'weather'
|
||||
influx_status_db = 'status'
|
||||
|
||||
# Icons
|
||||
fs_wind_icon = '../static/img/wind_icon.png'
|
||||
fs_pressure_icon = '../static/img/pressure_icon.png'
|
||||
fs_in_temperature_icon = '../static/img/inside_temp_icon.png'
|
||||
fs_out_temperature_icon = '../static/img/outside_temp_icon.png'
|
||||
fs_windgust_icon = '../static/img/wind_gust_icon.png'
|
||||
fs_winddirection_icon = '../static/img/wind_direction_icon.png'
|
||||
|
||||
# Functions
|
||||
def read_html(filename):
|
||||
read_path = _SCRIPT_PATH + _STATIC_DIR + filename + '.html'
|
||||
try:
|
||||
with open(read_path, 'r') as handle:
|
||||
return handle.read()
|
||||
except:
|
||||
return """<div>ERROR!</div>""" + read_path
|
||||
|
||||
# Set default structure
|
||||
|
||||
html_start = '<html>'
|
||||
body_start = '<body>'
|
||||
body_close = '</body>'
|
||||
html_close = '</html>'
|
||||
|
||||
class WeatherInfo(object):
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
header = read_html('header')
|
||||
menu_raw = read_html('top_menu')
|
||||
menu = menu_raw.format(energy = '', weather = 'active', status = '')
|
||||
body = self.body()
|
||||
footer = read_html('footer')
|
||||
result = header\
|
||||
+ menu\
|
||||
+ body\
|
||||
+ footer
|
||||
return result
|
||||
|
||||
def winddirName(self, direction):
|
||||
'''
|
||||
Function to get energy readings from InfluxDB.
|
||||
|
||||
returns:
|
||||
dict(): {"last": str(last_entry),
|
||||
"details": list(detailed_table),
|
||||
"watthours": str(watthours)}
|
||||
'''
|
||||
if 10.5 <= direction < 34.5:
|
||||
result = "NNE"
|
||||
elif 34.5 <= direction < 58.5:
|
||||
result = "NE"
|
||||
elif 58.5 <= direction < 82.5:
|
||||
result = "ENE"
|
||||
elif 82.5 <= direction < 106.5:
|
||||
result = "E"
|
||||
elif 106.5 <= direction < 130.5:
|
||||
result = "ESE"
|
||||
elif 130.5 <= direction < 154.5:
|
||||
result = "SE"
|
||||
elif 154.5 <= direction < 178.5:
|
||||
result = "SSE"
|
||||
elif 178.5 <= direction < 202.5:
|
||||
result = "S"
|
||||
elif 202.5 <= direction < 226.5:
|
||||
result = "SSW"
|
||||
elif 226.5 <= direction < 250.5:
|
||||
result = "SW"
|
||||
elif 250.5 <= direction < 274.5:
|
||||
result = "W"
|
||||
elif 274.5 <= direction < 298.5:
|
||||
result = "WNW"
|
||||
elif 298.5 <= direction < 322.5:
|
||||
result = "NW"
|
||||
elif 322.5 <= direction < 346.5:
|
||||
result = "NNW"
|
||||
elif 346.5 <= direction <= 359.9 or 0 < direction < 10.5:
|
||||
result = "N"
|
||||
elif direction == 0:
|
||||
result = "ERROR, windvan broken"
|
||||
else:
|
||||
result = "NaN"
|
||||
return result
|
||||
|
||||
def LastKnownState(self):
|
||||
'''
|
||||
Function to get energy readings from InfluxDB.
|
||||
|
||||
returns:
|
||||
dict(): {"last": str(last_entry),
|
||||
"details": list(detailed_table),
|
||||
"watthours": str(watthours)}
|
||||
'''
|
||||
influx_weather_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_weather_db
|
||||
)
|
||||
influx_status_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_status_db
|
||||
)
|
||||
|
||||
# General query
|
||||
query1 = "SELECT time, value FROM wind WHERE type = "
|
||||
query2 = "AND time > NOW() - 5m ORDER BY time DESC LIMIT 1"
|
||||
|
||||
# now parse the tag in the middle of the two q from above
|
||||
wind_speed_q = "{} '{}' {}".format(query1, "speed", query2)
|
||||
wind_direction_q = "{} '{}' {}".format(query1, "direction", query2)
|
||||
wind_gust_q = "{} '{}' {}".format(query1, "windgust", query2)
|
||||
hum_ext_q = "SELECT time, humidity FROM temphumi WHERE type = 'external' {}".format(query2)
|
||||
hum_int_q = "SELECT time, humidity FROM temphumi WHERE type = 'internal' {}".format(query2)
|
||||
press_q = "SELECT time, pressure FROM temphumi WHERE type = 'adjusted' {}".format(query2)
|
||||
presr_q = "SELECT time, pressure FROM temphumi WHERE type = 'raw' {}".format(query2)
|
||||
t_ext_q = "SELECT time, temperature FROM temphumi WHERE type = 'external' {}".format(query2)
|
||||
t_int_q = "SELECT time, temperature FROM temphumi WHERE type = 'internal' {}".format(query2)
|
||||
|
||||
|
||||
# the actual query to DB
|
||||
wind_speed = influx_weather_client.query(wind_speed_q)
|
||||
wind_direction = influx_weather_client.query(wind_direction_q)
|
||||
wind_gust = influx_weather_client.query(wind_gust_q)
|
||||
hum_ext = influx_weather_client.query(hum_ext_q)
|
||||
hum_int = influx_weather_client.query(hum_int_q)
|
||||
press = influx_weather_client.query(press_q)
|
||||
presr = influx_weather_client.query(presr_q)
|
||||
t_ext = influx_weather_client.query(t_ext_q)
|
||||
t_int = influx_weather_client.query(t_int_q)
|
||||
|
||||
|
||||
# returned is a list, in this case, we just need one value [0]
|
||||
result_windspeed = [speed for speed in wind_speed][0][0]
|
||||
result_winddir = [direction for direction in wind_direction][0][0]
|
||||
result_windgust = [gust for gust in wind_gust][0][0]
|
||||
result_hum_int = [y for y in hum_int][0][0]
|
||||
result_t_int = [c for c in t_int][0][0]
|
||||
result_press = [z for z in press][0][0]
|
||||
result_presr = [a for a in presr][0][0]
|
||||
result_t_ext = [b for b in t_ext][0][0]
|
||||
result_hum_ext = [x for x in hum_ext][0][0]
|
||||
|
||||
# Put the time to a uhman readable format, strip nanosecs
|
||||
time_stamp = time.strptime(result_windspeed['time'].split('.')[0],
|
||||
"%Y-%m-%dT%H:%M:%S")
|
||||
result = {}
|
||||
|
||||
# Construct the result to return
|
||||
result.update({"time": time_stamp})
|
||||
result.update({"speed": round(result_windspeed['value'], 1)})
|
||||
result.update({"direction": round(result_winddir['value'], 1)})
|
||||
result.update({"windgust": round(result_windgust['value'], 1)})
|
||||
result.update({"humidity_ext": round(result_hum_ext['humidity'], 1)})
|
||||
result.update({"humidity_int": round(result_hum_int['humidity'], 1)})
|
||||
result.update({"pressure": round(result_press['pressure'], 1)})
|
||||
result.update({"pressure_raw": round(result_presr['pressure'], 1)})
|
||||
result.update({"temp_ext": round(result_t_ext['temperature'], 1)})
|
||||
result.update({"temp_int": round(result_t_int['temperature'], 1)})
|
||||
return result
|
||||
|
||||
|
||||
def body(self):
|
||||
admin_preformat = read_html('weather_admin')
|
||||
current_weather = self.LastKnownState()
|
||||
admin_html = admin_preformat.format(
|
||||
timestamp = time.strftime("%d.%m.%Y %H:%M:%S",
|
||||
current_weather['time']),
|
||||
w_speed_icon = fs_wind_icon,
|
||||
w_speed_km = current_weather['speed'],
|
||||
w_speed_ms = round(current_weather['speed'] / 3.6, 1),
|
||||
w_gust_icon = fs_windgust_icon,
|
||||
w_gust_km = current_weather['windgust'],
|
||||
w_gust_ms = round(current_weather['windgust'] / 3.6, 1),
|
||||
w_dir_icon = fs_winddirection_icon,
|
||||
w_dir_name = self.winddirName(current_weather['direction']),
|
||||
w_dir_deg = current_weather['direction'],
|
||||
out_temp_icon = fs_out_temperature_icon,
|
||||
out_temp = current_weather['temp_ext'],
|
||||
in_temp_icon = fs_in_temperature_icon,
|
||||
in_temp = current_weather['temp_int'],
|
||||
pressure_icon = fs_pressure_icon,
|
||||
pressure = current_weather['pressure'],
|
||||
raw_pressure = current_weather['pressure_raw']
|
||||
)
|
||||
return admin_html
|
||||