initial commit

This commit is contained in:
Milan Toman
2018-07-02 10:59:57 +02:00
commit df2c704270
64 changed files with 28611 additions and 0 deletions

138
arduino/CC1101.h Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
Working version, flawlesly!

543
python/davis_etl.py Executable file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
Absolute web path throughout the project
Graphs share JS variables, separate them.

50
web/chttpd.py Executable file
View 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
View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

1
web/static/css/bootstrap-grid.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

459
web/static/css/bootstrap-reboot.css vendored Normal file
View 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 */

File diff suppressed because one or more lines are too long

View 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 */

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

6
web/static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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;
}

View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

52
web/static/img/53096.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

3535
web/static/js/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load Diff

7
web/static/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

9383
web/static/js/dygraph.js Normal file

File diff suppressed because it is too large Load Diff

View 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)
}
}())

View 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();
}

View 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);
}

View 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 [&deg;]</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 [&deg;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 [&deg;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
View 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

View 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
View 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>

View 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')">&lt;&lt;</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')">&gt;&gt;</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')">&lt;&lt;</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')">&gt;&gt;</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>

View 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>

View 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()">&lt;&lt;</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()">&gt;&gt;</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>

View 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} &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} &deg;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} &deg;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')">&lt;&lt;</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')">&gt;&gt;</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')">&lt;&lt;</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')">&gt;&gt;</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')">&lt;&lt;</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')">&gt;&gt;</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>

View 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} &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} &deg;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} &deg;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')">&lt;&lt;</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')">&gt;&gt;</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')">&lt;&lt;</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');">&gt;&gt;</button>
=======
<button type="button" class="btn btn-secondary" onclick="setForth('temphumi_monitor')">&gt;&gt;</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
View 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
View 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