From 37206847c58d73a09273cc0726c69e7c6e57b361 Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 9 May 2022 20:47:05 +0200 Subject: [PATCH] * added rx channel switching * switched to crc8 check for valid packet-payload --- tools/esp8266/CircularBuffer.h | 4 + tools/esp8266/app.cpp | 115 +++++++++++++------ tools/esp8266/app.h | 5 + tools/esp8266/config.h | 6 +- tools/esp8266/defines.h | 4 +- tools/esp8266/hmRadio.h | 166 ++++++++++++++++++--------- tools/esp8266/html/convert.py | 8 +- tools/esp8266/html/h/hoymiles_html.h | 6 +- tools/esp8266/html/h/index_html.h | 6 +- tools/esp8266/html/h/setup_html.h | 6 +- tools/esp8266/html/h/style_css.h | 6 +- 11 files changed, 219 insertions(+), 113 deletions(-) diff --git a/tools/esp8266/CircularBuffer.h b/tools/esp8266/CircularBuffer.h index aca8e417..8ad3d5df 100644 --- a/tools/esp8266/CircularBuffer.h +++ b/tools/esp8266/CircularBuffer.h @@ -70,6 +70,10 @@ class CircularBuffer { return m_fill == m_size; } + inline uint8_t getFill(void) const { + return m_fill; + } + /** Aquire record on front of the buffer, for writing. * After filling the record, it has to be pushed to actually * add it to the buffer. diff --git a/tools/esp8266/app.cpp b/tools/esp8266/app.cpp index f541fe23..8b8828df 100644 --- a/tools/esp8266/app.cpp +++ b/tools/esp8266/app.cpp @@ -3,6 +3,7 @@ #include "html/h/index_html.h" #include "html/h/setup_html.h" #include "html/h/hoymiles_html.h" +#include "html/h/debug_html.h" //----------------------------------------------------------------------------- @@ -16,6 +17,7 @@ app::app() : Main() { mMqttActive = false; mTicker = 0; + mRxTicker = 0; mShowRebootRequest = false; @@ -26,6 +28,8 @@ app::app() : Main() { //memset(mChannelStat, 0, sizeof(uint32_t) * 4); mSys = new HmSystemType(); + + memset(mPayload, 0, ((MAX_RF_PAYLOAD_SIZE-11) * MAX_NUM_PAC_PER_PAYLOAD)); } @@ -46,6 +50,7 @@ void app::setup(uint32_t timeout) { mWeb->on("/cmdstat", std::bind(&app::showStatistics, this)); mWeb->on("/hoymiles", std::bind(&app::showHoymiles, this)); mWeb->on("/livedata", std::bind(&app::showLiveData, this)); + mWeb->on("/debug", std::bind(&app::showDebug, this)); if(mSettingsValid) { uint64_t invSerial; @@ -62,10 +67,11 @@ void app::setup(uint32_t timeout) { DPRINTLN("add inverter: " + String(invName) + ", SN: " + String(invSerial, HEX) + ", type: " + String(invType)); } } - mEep->read(ADDR_INV_INTERVAL, &mSendInterval); - if(mSendInterval < 1) - mSendInterval = 1; - + // @TODO reenable + //mEep->read(ADDR_INV_INTERVAL, &mSendInterval); + //if(mSendInterval < 5) + // mSendInterval = 5; + mSendInterval = 5; // pinout mEep->read(ADDR_PINOUT, &mSys->Radio.pinCs); @@ -134,50 +140,53 @@ void app::setup(uint32_t timeout) { void app::loop(void) { Main::loop(); - if(!mSys->BufCtrl.empty()) { - uint8_t len, rptCnt; - packet_t *p = mSys->BufCtrl.getBack(); - if(mSerialDebug) - mSys->Radio.dumpBuf("RAW ", p->packet, MAX_RF_PAYLOAD_SIZE); - - if(mSys->Radio.checkCrc(p->packet, &len, &rptCnt)) { - // process buffer only on first occurrence - if((0 != len) && (0 == rptCnt)) { - uint8_t *cmd = &p->packet[11]; - //DPRINTLN("CMD " + String(*cmd, HEX)); - //mSys->Radio.dumpBuf("Payload ", p->packet, len); - - Inverter<> *iv = mSys->findInverter(&p->packet[3]); - if(NULL != iv) { - for(uint8_t i = 0; i < iv->listLen; i++) { - if(iv->assign[i].cmdId == *cmd) - iv->addValue(i, &p->packet[11]); + if(checkTicker(&mRxTicker, 5)) { + mSys->Radio.switchRxCh(); + if(!mSys->BufCtrl.empty()) { + uint8_t len, rptCnt; + packet_t *p = mSys->BufCtrl.getBack(); + + //if(mSerialDebug) + // mSys->Radio.dumpBuf("RAW ", p->packet, MAX_RF_PAYLOAD_SIZE); + + if(mSys->Radio.checkPaketCrc(p->packet, &len, &rptCnt, p->rxCh)) { + // process buffer only on first occurrence + if((0 != len) && (0 == rptCnt)) { + uint8_t *cmd = &p->packet[9]; + //DPRINTLN("CMD " + String(*cmd, HEX)); + if(mSerialDebug) + mSys->Radio.dumpBuf("Payload ", p->packet, len); + + Inverter<> *iv = mSys->findInverter(&p->packet[1]); + if(NULL != iv) { + for(uint8_t i = 0; i < iv->listLen; i++) { + if(iv->assign[i].cmdId == *cmd) + iv->addValue(i, &p->packet[9]); + } + iv->doCalculations(); + //memcpy(mPayload[(*cmd & 0x7F) - 1], &p->packet[9], MAX_RF_PAYLOAD_SIZE - 11); } - iv->doCalculations(); - } - if(*cmd == 0x01) mCmds[0]++; - else if(*cmd == 0x02) mCmds[1]++; - else if(*cmd == 0x03) mCmds[2]++; - else if(*cmd == 0x81) mCmds[3]++; - else if(*cmd == 0x82) mCmds[4]++; - else if(*cmd == 0x83) mCmds[5]++; - else if(*cmd == 0x84) mCmds[6]++; - else mCmds[7]++; - - /*if(p->sendCh == 23) mChannelStat[0]++; - else if(p->sendCh == 40) mChannelStat[1]++; - else if(p->sendCh == 61) mChannelStat[2]++; - else mChannelStat[3]++;*/ + if(*cmd == 0x01) mCmds[0]++; + else if(*cmd == 0x02) mCmds[1]++; + else if(*cmd == 0x03) mCmds[2]++; + else if(*cmd == 0x81) mCmds[3]++; + else if(*cmd == 0x82) mCmds[4]++; + else if(*cmd == 0x83) mCmds[5]++; + else if(*cmd == 0x84) mCmds[6]++; + else mCmds[7]++; + } } + mSys->BufCtrl.popBack(); } - mSys->BufCtrl.popBack(); } if(checkTicker(&mTicker, 1000)) { if(++mSendTicker >= mSendInterval) { mSendTicker = 0; + if(!mSys->BufCtrl.empty()) + DPRINTLN("recbuf not empty! #" + String(mSys->BufCtrl.getFill())); Inverter<> *inv; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { inv = mSys->getInverterByPos(i); @@ -504,6 +513,36 @@ void app::showLiveData(void) { } +//----------------------------------------------------------------------------- +void app::showDebug() { + if(mWeb->args() > 0) { + /*String html; + + for(uint8_t i = 0; i < MAX_NUM_PAC_PER_PAYLOAD; i ++) { + for(uint8_t j = 0; j < (MAX_RF_PAYLOAD_SIZE- 11); j++) { + if(mPayload[i][j] < 0x10) + html += "0"; + html += String(mPayload[i][j], HEX) + " "; + } + html += "\n"; + } + html += "CMDS: "; + for(uint8_t i = 0; i < DBG_CMD_LIST_LEN; i ++) { + html += String(mCmds[i]) + String(" "); + } + html += "\n------------------";*/ + + mWeb->send(200, "text/plain", "na"); + } + else { + String html = FPSTR(debug_html); + html.replace("{DEVICE}", mDeviceName); + html.replace("{VERSION}", mVersion); + mWeb->send(200, "text/html", html); + } +} + + //----------------------------------------------------------------------------- void app::saveValues(bool webSend = true) { Main::saveValues(false); // general configuration diff --git a/tools/esp8266/app.h b/tools/esp8266/app.h index f60405a1..f179af32 100644 --- a/tools/esp8266/app.h +++ b/tools/esp8266/app.h @@ -47,6 +47,7 @@ class app : public Main { void showStatistics(void); void showHoymiles(void); void showLiveData(void); + void showDebug(void); void saveValues(bool webSend); void updateCrc(void); @@ -82,6 +83,8 @@ class app : public Main { bool mSerialValues; bool mSerialDebug; + uint32_t mRxTicker; + // mqtt mqtt mMqtt; uint16_t mMqttTicker; @@ -89,6 +92,8 @@ class app : public Main { bool mMqttActive; uint16_t mSerialTicker; uint16_t mSerialInterval; + + uint8_t mPayload[MAX_NUM_PAC_PER_PAYLOAD][MAX_RF_PAYLOAD_SIZE-11]; }; #endif /*__APP_H__*/ diff --git a/tools/esp8266/config.h b/tools/esp8266/config.h index 009d475d..632817da 100644 --- a/tools/esp8266/config.h +++ b/tools/esp8266/config.h @@ -17,7 +17,7 @@ // CONFIGURATION - COMPILE TIME //------------------------------------- // time in seconds how long the station info (ssid + pwd) will be tried -#define WIFI_TRY_CONNECT_TIME 15 +#define WIFI_TRY_CONNECT_TIME 30 // time during the ESP will act as access point on connection failure (to // station) in seconds @@ -36,7 +36,9 @@ #define MAX_NAME_LENGTH 16 // maximum buffer length of packet received / sent to RF24 module -#define MAX_RF_PAYLOAD_SIZE 64 +#define MAX_RF_PAYLOAD_SIZE 32 + +#define MAX_NUM_PAC_PER_PAYLOAD 4 // changes the style of "/setup" page, visualized = nicer #define LIVEDATA_VISUALIZED diff --git a/tools/esp8266/defines.h b/tools/esp8266/defines.h index 192a1a9b..aa94bc77 100644 --- a/tools/esp8266/defines.h +++ b/tools/esp8266/defines.h @@ -16,12 +16,12 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 3 -#define VERSION_PATCH 6 +#define VERSION_PATCH 7 //------------------------------------- typedef struct { - uint8_t sendCh; + uint8_t rxCh; uint8_t packet[MAX_RF_PAYLOAD_SIZE]; } packet_t; diff --git a/tools/esp8266/hmRadio.h b/tools/esp8266/hmRadio.h index 6e84dfb7..e7e67c8e 100644 --- a/tools/esp8266/hmRadio.h +++ b/tools/esp8266/hmRadio.h @@ -13,6 +13,7 @@ #define DTU_RADIO_ID ((uint64_t)0x1234567801ULL) #define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL) +#define RX_LOOP_CNT 400 const char* const rf24AmpPower[] = {"MIN", "LOW", "HIGH", "MAX"}; @@ -47,13 +48,17 @@ template full()) { p = mBufCtrl->getFront(); memset(p->packet, 0xcc, MAX_RF_PAYLOAD_SIZE); - p->sendCh = mSendChannel; + p->rxCh = mRxChIdx; len = mNrf24.getPayloadSize(); if(len > MAX_RF_PAYLOAD_SIZE) len = MAX_RF_PAYLOAD_SIZE; @@ -125,53 +129,55 @@ class HmRadio { } uint8_t getDefaultChannel(void) { - return mChanOut[2]; + return mTxChLst[0]; } - uint8_t getLastChannel(void) { - return mChanOut[mChanIdx]; + /*uint8_t getLastChannel(void) { + return mTxChLst[mTxChIdx]; } uint8_t getNxtChannel(void) { - if(++mChanIdx >= 4) - mChanIdx = 0; - return mChanOut[mChanIdx]; - } + if(++mTxChIdx >= 4) + mTxChIdx = 0; + return mTxChLst[mTxChIdx]; + }*/ void sendTimePacket(uint64_t invId, uint32_t ts) { sendCmdPacket(invId, 0x15, 0x80, false); - mSendBuf[10] = 0x0b; // cid - mSendBuf[11] = 0x00; - CP_U32_LittleEndian(&mSendBuf[12], ts); - mSendBuf[19] = 0x05; + mTxBuf[10] = 0x0b; // cid + mTxBuf[11] = 0x00; + CP_U32_LittleEndian(&mTxBuf[12], ts); + mTxBuf[19] = 0x05; - uint16_t crc = crc16(&mSendBuf[10], 14); - mSendBuf[24] = (crc >> 8) & 0xff; - mSendBuf[25] = (crc ) & 0xff; - mSendBuf[26] = crc8(mSendBuf, 26); + uint16_t crc = crc16(&mTxBuf[10], 14); + mTxBuf[24] = (crc >> 8) & 0xff; + mTxBuf[25] = (crc ) & 0xff; + mTxBuf[26] = crc8(mTxBuf, 26); - sendPacket(invId, mSendBuf, 27); + sendPacket(invId, mTxBuf, 27, true); } void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t cmd, bool calcCrc = true) { - memset(mSendBuf, 0, MAX_RF_PAYLOAD_SIZE); - mSendBuf[0] = mid; // message id - CP_U32_BigEndian(&mSendBuf[1], (invId >> 8)); - CP_U32_BigEndian(&mSendBuf[5], (DTU_ID >> 8)); - mSendBuf[9] = cmd; + memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE); + mTxBuf[0] = mid; // message id + CP_U32_BigEndian(&mTxBuf[1], (invId >> 8)); + CP_U32_BigEndian(&mTxBuf[5], (DTU_ID >> 8)); + mTxBuf[9] = cmd; if(calcCrc) { - mSendBuf[10] = crc8(mSendBuf, 10); - sendPacket(invId, mSendBuf, 11); + mTxBuf[10] = crc8(mTxBuf, 10); + sendPacket(invId, mTxBuf, 11, false); } } - bool checkCrc(uint8_t buf[], uint8_t *len, uint8_t *rptCnt) { + bool checkPaketCrc(uint8_t buf[], uint8_t *len, uint8_t *rptCnt, uint8_t rxCh) { *len = (buf[0] >> 2); - for (int16_t i = MAX_RF_PAYLOAD_SIZE - 1; i >= 0; i--) { - buf[i] = ((buf[i] >> 7) | ((i > 0) ? (buf[i-1] << 1) : 0x00)); + if(*len > (MAX_RF_PAYLOAD_SIZE - 2)) + *len = MAX_RF_PAYLOAD_SIZE - 2; + for(uint8_t i = 1; i < (*len + 1); i++) { + buf[i-1] = (buf[i] << 1) | (buf[i+1] >> 7); } - uint16_t crc = crc16nrf24(buf, BIT_CNT(*len + 2), 7, mDtuIdCrc); - bool valid = (crc == ((buf[*len+2] << 8) | (buf[*len+3]))); + uint8_t crc = crc8(buf, *len-1); + bool valid = (crc == buf[*len-1]); if(valid) { if(mLastCrc == crc) @@ -181,16 +187,34 @@ class HmRadio { *rptCnt = 0; mLastCrc = crc; } + mRxStat[(buf[9] & 0x7F)-1]++; + mRxChStat[(buf[9] & 0x7F)-1][rxCh & 0x7]++; } + /*else { + DPRINT("CRC wrong: "); + DHEX(crc); + DPRINT(" != "); + DHEX(buf[*len-1]); + DPRINTLN(""); + }*/ return valid; } + bool switchRxCh(uint8_t addLoop = 0) { + mRxLoopCnt += addLoop; + if(mRxLoopCnt != 0) { + mRxLoopCnt--; + mNrf24.stopListening(); + mNrf24.setChannel(getRxNxtChannel()); + mNrf24.startListening(); + } + return (0 == mRxLoopCnt); // receive finished + } + void dumpBuf(const char *info, uint8_t buf[], uint8_t len) { DPRINT(String(info)); for(uint8_t i = 0; i < len; i++) { - if(buf[i] < 10) - DPRINT("0"); DHEX(buf[i]); DPRINT(" "); } @@ -209,26 +233,41 @@ class HmRadio { uint32_t mSendCnt; private: - void sendPacket(uint64_t invId, uint8_t buf[], uint8_t len) { + void sendPacket(uint64_t invId, uint8_t buf[], uint8_t len, bool clear=false) { //DPRINTLN("sent packet: #" + String(mSendCnt)); //dumpBuf("SEN ", buf, len); DISABLE_IRQ; mNrf24.stopListening(); - #ifdef CHANNEL_HOP - mSendChannel = getNxtChannel(); - #else - mSendChannel = getDefaultChannel(); - #endif - mNrf24.setChannel(mSendChannel); - //DPRINTLN("CH: " + String(mSendChannel)); + if(clear) { + uint8_t cnt = 4; + for(uint8_t i = 0; i < 4; i ++) { + DPRINT(String(mRxStat[i]) + " ("); + for(uint8_t j = 0; j < 4; j++) { + DPRINT(String(mRxChStat[i][j])); + } + DPRINT(") "); + if(0 != mRxStat[i]) + cnt--; + } + if(cnt == 0) + DPRINTLN(" -> all"); + else + DPRINTLN(" -> missing: " + String(cnt)); + memset(mRxStat, 0, 4); + memset(mRxChStat, 0, 4*8); + mRxLoopCnt = RX_LOOP_CNT; + } + + mTxCh = getDefaultChannel(); + mNrf24.setChannel(mTxCh); mNrf24.openWritingPipe(invId); // TODO: deprecated mNrf24.setCRCLength(RF24_CRC_16); mNrf24.enableDynamicPayloads(); mNrf24.setAutoAck(true); - mNrf24.setRetries(3, 15); + mNrf24.setRetries(3, 15); // 3*250us and 15 loops -> 11.25ms mNrf24.write(buf, len); @@ -240,14 +279,21 @@ class HmRadio { mNrf24.disableDynamicPayloads(); mNrf24.setCRCLength(RF24_CRC_DISABLED); - mNrf24.setChannel(DEFAULT_RECV_CHANNEL); + mRxChIdx = 0; + mNrf24.setChannel(mRxChLst[mRxChIdx]); mNrf24.startListening(); RESTORE_IRQ; mSendCnt++; } - void calcDtuCrc(void) { + uint8_t getRxNxtChannel(void) { + if(++mRxChIdx >= 4) + mRxChIdx = 0; + return mRxChLst[mRxChIdx]; + } + + /*void calcDtuCrc(void) { uint64_t addr = DTU_RADIO_ID; uint8_t tmp[5]; for(int8_t i = 4; i >= 0; i--) { @@ -255,18 +301,26 @@ class HmRadio { addr >>= 8; } mDtuIdCrc = crc16nrf24(tmp, BIT_CNT(5)); - } + }*/ - uint8_t mChanOut[4]; - uint8_t mChanIdx; - uint16_t mDtuIdCrc; + uint8_t mTxCh; + uint8_t mTxChLst[1]; + //uint8_t mTxChIdx; + + uint8_t mRxChLst[4]; + uint8_t mRxChIdx; + uint8_t mRxStat[4]; + uint8_t mRxChStat[4][8]; + uint16_t mRxLoopCnt; + + //uint16_t mDtuIdCrc; uint16_t mLastCrc; uint8_t mRptCnt; RF24 mNrf24; - uint8_t mSendChannel; BUFFER *mBufCtrl; - uint8_t mSendBuf[MAX_RF_PAYLOAD_SIZE]; + uint8_t mTxBuf[MAX_RF_PAYLOAD_SIZE]; + }; #endif /*__RADIO_H__*/ diff --git a/tools/esp8266/html/convert.py b/tools/esp8266/html/convert.py index 4b58681f..108a8d4b 100644 --- a/tools/esp8266/html/convert.py +++ b/tools/esp8266/html/convert.py @@ -15,14 +15,16 @@ def convert2Header(inFile): data = re.sub(r"(\;|\}|\:|\{)\s+", r'\1', data) # whitespaces inner css define = inFile.split(".")[0].upper() + define2 = inFile.split(".")[1].upper() f = open(outName, "w") - f.write("#ifndef __{}_H__\n".format(define)) - f.write("#define __{}_H__\n".format(define)) + f.write("#ifndef __{}_{}_H__\n".format(define, define2)) + f.write("#define __{}_{}_H__\n".format(define, define2)) f.write("const char {}[] PROGMEM = \"{}\";\n".format(inFile.replace(".", "_"), data)) - f.write("#endif /*__{}_H__*/\n".format(define)) + f.write("#endif /*__{}_{}_H__*/\n".format(define, define2)) f.close() convert2Header("index.html") convert2Header("setup.html") convert2Header("hoymiles.html") convert2Header("style.css") +convert2Header("debug.html") diff --git a/tools/esp8266/html/h/hoymiles_html.h b/tools/esp8266/html/h/hoymiles_html.h index 8401ebac..5e3a43e1 100644 --- a/tools/esp8266/html/h/hoymiles_html.h +++ b/tools/esp8266/html/h/hoymiles_html.h @@ -1,4 +1,4 @@ -#ifndef __HOYMILES_H__ -#define __HOYMILES_H__ +#ifndef __HOYMILES_HTML_H__ +#define __HOYMILES_HTML_H__ const char hoymiles_html[] PROGMEM = "Index - {DEVICE}

AHOY - {DEVICE}

Every 10 seconds the values are updated

© 2022

Home

AHOY :: {VERSION}

"; -#endif /*__HOYMILES_H__*/ +#endif /*__HOYMILES_HTML_H__*/ diff --git a/tools/esp8266/html/h/index_html.h b/tools/esp8266/html/h/index_html.h index 6705332a..c75e9d64 100644 --- a/tools/esp8266/html/h/index_html.h +++ b/tools/esp8266/html/h/index_html.h @@ -1,4 +1,4 @@ -#ifndef __INDEX_H__ -#define __INDEX_H__ +#ifndef __INDEX_HTML_H__ +#define __INDEX_HTML_H__ const char index_html[] PROGMEM = "Index - {DEVICE}

AHOY - {DEVICE}

Visualization

Setup

Uptime:

Time:

Statistics:

This project was started from this discussion. (Mikrocontroller.net)
New updates can be found on Github: https://github.com/grindylow/ahoy

Please report issues using the feature provided by Github.

© 2022

Update Firmware

AHOY :: {VERSION}

Reboot

"; -#endif /*__INDEX_H__*/ +#endif /*__INDEX_HTML_H__*/ diff --git a/tools/esp8266/html/h/setup_html.h b/tools/esp8266/html/h/setup_html.h index 07bac0d4..49f8e9c3 100644 --- a/tools/esp8266/html/h/setup_html.h +++ b/tools/esp8266/html/h/setup_html.h @@ -1,4 +1,4 @@ -#ifndef __SETUP_H__ -#define __SETUP_H__ +#ifndef __SETUP_HTML_H__ +#define __SETUP_HTML_H__ const char setup_html[] PROGMEM = "Setup - {DEVICE}

Setup

Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.

WiFi

Device Host Name

ERASE SETTINGS (not WiFi)

Inverter

{INVERTERS}

General

Pinout (Wemos)

{PINOUT}

Radio (NRF24L01+)

MQTT

Serial Console



 

"; -#endif /*__SETUP_H__*/ +#endif /*__SETUP_HTML_H__*/ diff --git a/tools/esp8266/html/h/style_css.h b/tools/esp8266/html/h/style_css.h index 1c79cef5..b64ceeb6 100644 --- a/tools/esp8266/html/h/style_css.h +++ b/tools/esp8266/html/h/style_css.h @@ -1,4 +1,4 @@ -#ifndef __STYLE_H__ -#define __STYLE_H__ +#ifndef __STYLE_CSS_H__ +#define __STYLE_CSS_H__ const char style_css[] PROGMEM = "h1 {margin:0;padding:20pt;font-size:22pt;color:#fff;background-color:#006ec0;display:block;text-transform:uppercase;}html, body {font-family:Arial;margin:0;padding:0;}p {text-align:justify;font-size:13pt;}.des {margin-top:35px;font-size:13pt;color:#006ec0;}.subdes {font-size:12pt;color:#006ec0;margin-left:7px;}a:link, a:visited {text-decoration:none;font-size:13pt;color:#006ec0;}a:hover, a:focus {color:#f00;}a.erase {background-color:#006ec0;color:#fff;padding:7px;display:inline-block;margin-top:30px;float:right;}#content {padding:15px 15px 60px 15px;}#footer {position:fixed;bottom:0px;height:45px;background-color:#006ec0;width:100%;border-top:5px solid #fff;}#footer p, #footer a {color:#fff;padding:0 7px 0 7px;font-size:10pt !important;}div.content {background-color:#fff;padding-bottom:65px;overflow:auto;}input, select {padding:7px;font-size:13pt;}input.text, select {width:70%;box-sizing:border-box;margin-bottom:10px;border:1px solid #ccc;}input.btn {background-color:#006ec0;color:#fff;border:0px;float:right;margin:10px 0 30px;text-transform:uppercase;}input.cb {margin-bottom:20px;}label {width:20%;display:inline-block;font-size:12pt;padding-right:10px;margin-left:10px;}.left {float:left;}.right {float:right;}div.ch-iv {width:100%;background-color:#32b004;display:inline-block;margin-bottom:20px;padding-bottom:20px;overflow:auto;}div.ch {width:250px;min-height:420px;background-color:#006ec0;display:inline-block;margin-right:20px;margin-bottom:20px;overflow:auto;padding-bottom:20px;}div.ch .value, div.ch .info, div.ch .head, div.ch-iv .value, div.ch-iv .info, div.ch-iv .head {color:#fff;display:block;width:100%;text-align:center;}.subgrp {float:left;width:250px;}div.ch .unit, div.ch-iv .unit {font-size:19px;margin-left:10px;}div.ch .value, div.ch-iv .value {margin-top:20px;font-size:30px;}div.ch .info, div.ch-iv .info {margin-top:3px;font-size:10px;}div.ch .head {background-color:#003c80;padding:10px 0 10px 0;}div.ch-iv .head {background-color:#1c6800;padding:10px 0 10px 0;}div.iv {max-width:1060px;}div.ch:last-child {margin-right:0px !important;}#note {margin:50px 10px 10px 10px;padding-top:10px;width:100%;border-top:1px solid #bbb;}"; -#endif /*__STYLE_H__*/ +#endif /*__STYLE_CSS_H__*/