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
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
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
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
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
View File
@@ -0,0 +1 @@
Working version, flawlesly!
+543
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
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
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
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
Executable
+2
View File
@@ -0,0 +1,2 @@
Absolute web path throughout the project
Graphs share JS variables, separate them.
Executable
+50
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()
Executable
+497
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
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+459
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
+1
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 */
@@ -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
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+70
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;
}
+75
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
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);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+52
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

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
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+24
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)
}
}())
+120
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();
}
+174
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);
}
+296
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);
}
Executable
+106
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
+7
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
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>
+65
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>
+20
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>
+74
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>
+108
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>
+280
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>
Executable
+317
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
Executable
+200
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