From f06e658bf0f161866e397d7f9e26987a055ec967 Mon Sep 17 00:00:00 2001 From: Milan Ventil Toman Date: Fri, 29 May 2020 15:05:46 +0200 Subject: [PATCH] github upload --- WiFi.mpy | Bin 0 -> 1610 bytes WiFi.py | 105 ++++++++++++ boot.py | 3 + cc1101_davis.mpy | Bin 0 -> 7979 bytes cc1101_davis.py | 390 ++++++++++++++++++++++++++++++++++++++++++++ davis_decode.mpy | Bin 0 -> 3139 bytes davis_decode.py | 223 +++++++++++++++++++++++++ inet.conf | 20 +++ influx_structure.md | 158 ++++++++++++++++++ main.py | 89 ++++++++++ readme.md | 371 +++++++++++++++++++++++++++++++++++++++++ 11 files changed, 1359 insertions(+) create mode 100644 WiFi.mpy create mode 100644 WiFi.py create mode 100644 boot.py create mode 100644 cc1101_davis.mpy create mode 100644 cc1101_davis.py create mode 100644 davis_decode.mpy create mode 100644 davis_decode.py create mode 100644 inet.conf create mode 100644 influx_structure.md create mode 100644 main.py create mode 100644 readme.md diff --git a/WiFi.mpy b/WiFi.mpy new file mode 100644 index 0000000000000000000000000000000000000000..cc403981ddc1d500fa06ac41c922c731282974e8 GIT binary patch literal 1610 zcmZWp+iu%N5MAoxi+mAD(XWo$(puju}Skldcn=!xwDn_ukWUbv+WHZxl z>a~;>bFsneNX*UBqkX&+^RP?0Qfr_@_*|P>yQvQ)M=_>F)u`2v)%F35=M6HBPhT_fHj~{d#tr|~_aQKI%_W|H_IZp8FIvkHE*9ZJ zktm;s3@G;n0@Xr)%#g;hqI9H| zvGiO;MfH3CyiE8UH(YF9wgWTFya2>kPjJ1Y+C+I!(*^MR3#U+0!m`x5rV&7Lz(T^6 znHqE&XoPnZbH#usHPwVoO^4>80!OOGPO{cgV7Ft;m-SQhUo;wpkH3K}O;;ceYZx=R zt_ig!)D=@dHJ-u0$GCg*)~Dfch~t-+R#sNhg(&FT!MPXcO>Y33xp6KKujx5vZpmt( zf(GKE#d`);h4iSM`k+@KCt5Y*s6Kd#kb{%@;M)p0)@U1bIWIqmfGQ1^{}Mm2-ef3% zMTfci&EYC97Yc7U@BRY<>=$GL^_Zf<2A?bzbH#Nyng~=7W~Zy825W8Dtf|LJ1D>en z;W{depPaovZ>0g681(xOW{*zXcU#+$(7n5>8>__#x4j?X_{b{HqlF*913SnLV@eHG zHq$?&Q#~SSH2h{1xjez63(i)V3FhJ@5Cub9auhz ztHDfs_oVP}|NjKKuSxJae>5RdP83WpnrVk%J3x>(Dt3@SLK_-1_-bgcp}WF?gAy8% zoywO|I#()R_+EnfT=9LdQc4!z_e3|q1$Z0Bl*EKoO~%XVE9YAmIJ&ROl}mT_0xrg@ zIK*(?>(pj$cQ=_WBi)ZgQ2(eJFJ?2@)H>Yi%11~C-44_`SQtEP)s7U1W}>c`rlK34 tT)HN8UDFL5#kmYdv3O{h>hUo=(C~EOK|)uFe>0ce%A}x}*(_su{R{jiyr2L8 literal 0 HcmV?d00001 diff --git a/WiFi.py b/WiFi.py new file mode 100644 index 0000000..2c6408c --- /dev/null +++ b/WiFi.py @@ -0,0 +1,105 @@ +import network +import utime +import machine + +_DEBUG = False + + +class NetSet(object): + + def __init__(self, wifi_type): + self.sta = network.WLAN(network.STA_IF) + self.ap = network.WLAN(network.AP_IF) + if wifi_type == 'infra': + if _DEBUG: + print("Disabling AP") + self.ap.active(False) + if _DEBUG: + print("Activating INFRA") + self.sta.active(True) + self.sta.isconnected() # False, it should be # Comments by Yoda + self._SSID = None + self._PASS = None + self._TIMEOUT = None + elif wifi_type == 'ap': + if _DEBUG: + print("Disabling INFRA") + self.ap.active(True) + if _DEBUG: + print("Activating AP") + self.sta.active(False) + self.sta.isconnected() # False, it should be # Comments by Yoda + self._SSID = None + self._PASS = None + self._TIMEOUT = None + + def connectInfraGo(self, _timeout): + if _DEBUG: + print("Connecting to infra") + self.sta.connect(self._SSID, self._PASS) + if _DEBUG: + print("Let's wait for the network to come up") + while not (self.sta.isconnected()): + if _timeout > 0: + print("Trying... {} more times".format(_timeout)) + utime.sleep_ms(1001) + _timeout -= 1 + else: + print("Out of retrys") + return False + network_config = self.sta.ifconfig() + return network_config + + def connectInfra(self, _SSID, _PASS, _TIMEOUT): + self._SSID = _SSID + self._PASS = _PASS + try: + net_conf_result = self.connectInfraGo(_TIMEOUT) + utime.sleep_ms(100) + if net_conf_result: + return { + 'ip': net_conf_result[0], + 'mask': net_conf_result[1], + 'gw': net_conf_result[2], + 'dns': net_conf_result[3] } + else: + return False + except Exception as e: + print("ERROR: Network configuration failed with: {}".format(e)) + return False + + def connectAp(self): + # TBI + pass + + def readNetworkConfig(self): + self.config_dict = {} + try: + with open('inet.conf', 'r') as conf_handler: + for item in conf_handler.readlines(): + option = item.split("=")[0].strip() + if len(item.split("=")) == 2 and '#' not in option: + value = item.split("=")[1].strip() + self.config_dict.update({option: value}) + else: + if _DEBUG: + if '#' in option: + print(b"COMMENT in config") + else: + print(b"WARNING: Fucked up option, make it better") + except Exception as e: + if _DEBUG: + print("WARNING: Errors in INFRA config, still going for AP") + return False + if _DEBUG: + print("CONFIG DICT: {}".format(self.config_dict)) + self._SSID = self.config_dict['_SSID'] + self._PASS = self.config_dict['_PASS'] + self._TIMEOUT = int(self.config_dict['_TIMEOUT']) + self._INFLUX_HOST = self.config_dict['_INFLUX_HOST'] + self._INFLUX_PORT = self.config_dict['_INFLUX_PORT'] + self._INFLUX_USER = self.config_dict['_INFLUX_USER'] + self._INFLUX_PASS = self.config_dict['_INFLUX_PASS'] + self._INF_DB_WEATHER = self.config_dict['_INF_DB_WEATHER'] + self._INF_DB_STATUS = self.config_dict['_INF_DB_STATUS'] + self._INF_DB_RAW = self.config_dict['_INF_DB_RAW'] diff --git a/boot.py b/boot.py new file mode 100644 index 0000000..fea6b0e --- /dev/null +++ b/boot.py @@ -0,0 +1,3 @@ +# This file is executed on every boot (including wake-boot from deepsleep) +import gc +gc.collect() diff --git a/cc1101_davis.mpy b/cc1101_davis.mpy new file mode 100644 index 0000000000000000000000000000000000000000..384f59a96e24619aeabb63b1b75a16107dfd542e GIT binary patch literal 7979 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 literal 0 HcmV?d00001 diff --git a/cc1101_davis.py b/cc1101_davis.py new file mode 100644 index 0000000..35a022d --- /dev/null +++ b/cc1101_davis.py @@ -0,0 +1,390 @@ +import utime +import gc +import machine + +ss = machine.Pin(15,machine.Pin.OUT) # Chip select / HCS +so = machine.Pin(12) # HMISO +si = machine.Pin(13) # HMOSI +sck = machine.Pin(14) # HSCLK +interrupt = machine.Pin(2) # Interrupt for packet available + +gc.collect() + +class CC1101(object): + def __init__(self): + self.debug = 1 + self.hspi = machine.SPI(1, baudrate=600000, polarity=0, phase=0) + self.PA_TABLE = [0xC0, 0xC0, 0xC0, 0xC0, + 0xC0, 0xC0, 0xC0, 0xC0] + + self.FREQ_2 = [0x21, 0x21, 0x21, 0x21, 0x21] + self.FREQ_1 = [0x62, 0x65, 0x67, 0x64, 0x66] + self.FREQ_0 = [0xE2, 0x40, 0x9D, 0x11, 0x6F] + + self.CRC_TABLE = [ + 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] + self.BUFFER_SIZE = 16 + self.DAVIS_PACKET_LENGTH = 10 + self.rxBuffer = [] + self.rxBufferIndex = 0 + self.rxBufferLength = 0 + self.hopIndex = 0 + self.freqComp = [0, 0, 0, 0, 0] + + # CC1101 Transfer Types. + self.WRITE_BURST = 0x40 + self.READ_SINGLE = 0x80 + self.READ_BURST = 0xC0 + + # Davis Register Configuration Settings. + self.DAVIS_IOCFG2 = 0x2E # GDO2 Output Pin Configuration + self.DAVIS_IOCFG1 = 0x2E # GDO1 Output Pin Configuration + self.DAVIS_IOCFG0 = 0x01 # GDO0 Output Pin Configuration + self.DAVIS_FIFOTHR = 0x42 # RX FIFO and TX FIFO Thresholds + self.DAVIS_SYNC1 = 0xCB # Synchronization word, high byte + self.DAVIS_SYNC0 = 0x89 # Synchronization word, low byte + self.DAVIS_PKTLEN = 0x0A # Packet Length + self.DAVIS_PKTCTRL1 = 0xC4 # Packet Automation Control + self.DAVIS_PKTCTRL0 = 0x00 # Packet Automation Control + self.DAVIS_ADDR = 0x00 # Device Address + self.DAVIS_CHANNR = 0x00 # Channel Number + self.DAVIS_FSCTRL1 = 0x06 # Frequency Synthesizer Control + self.DAVIS_FSCTRL0 = 0xF0 # Frequency Synthesizer Control + self.DAVIS_FREQ2 = 0x23 # Frequency Control Word, High Byte + self.DAVIS_FREQ1 = 0x0D # Frequency Control Word, Middle Byte + self.DAVIS_FREQ0 = 0x97 # Frequency Control Word, Low Byte + self.DAVIS_MDMCFG4 = 0xC9 # Modem Configuration + self.DAVIS_MDMCFG3 = 0x83 # Modem Configuration + self.DAVIS_MDMCFG2 = 0x12 # Modem Configuration + self.DAVIS_MDMCFG1 = 0x21 # Modem Configuration + self.DAVIS_MDMCFG0 = 0xF9 # Modem Configuration + self.DAVIS_DEVIATN = 0x24 # Modem Deviation Setting + self.DAVIS_MCSM2 = 0x07 # Main Radio Control State Machine Configuration + self.DAVIS_MCSM1 = 0x00 # Main Radio Control State Machine Configuration + self.DAVIS_MCSM0 = 0x18 # Main Radio Control State Machine Configuration + self.DAVIS_FOCCFG = 0x16 # Frequency Offset Compensation Configuration + self.DAVIS_BSCFG = 0x6C # Bit Synchronization Configuration + self.DAVIS_AGCCTRL2 = 0x43 # AGC Control + self.DAVIS_AGCCTRL1 = 0x40 # AGC Control + self.DAVIS_AGCCTRL0 = 0x91 # AGC Control + self.DAVIS_WOREVT1 = 0x87 # High Byte Event0 Timeout + self.DAVIS_WOREVT0 = 0x6B # Low Byte Event0 Timeout + self.DAVIS_WORCTRL = 0xF8 # Wake On Radio Control + self.DAVIS_FREND1 = 0x56 # Front End RX Configuration + self.DAVIS_FREND0 = 0x10 # Front End TX Configuration + self.DAVIS_FSCAL3 = 0xEF # Frequency Synthesizer Calibration + self.DAVIS_FSCAL2 = 0x2B # Frequency Synthesizer Calibration + self.DAVIS_FSCAL1 = 0x2F # Frequency Synthesizer Calibration + self.DAVIS_FSCAL0 = 0x1F # Frequency Synthesizer Calibration + self.DAVIS_RCCTRL1 = 0x00 # RC Oscillator Configuration + self.DAVIS_RCCTRL0 = 0x00 # RC Oscillator Configuration + self.DAVIS_FSTEST = 0x59 # Frequency Synthesizer Calibration Control + self.DAVIS_PTEST = 0x7F # Production Test + self.DAVIS_AGCTEST = 0x3F # AGC Test + self.DAVIS_TEST2 = 0x81 # Various Test Settings + self.DAVIS_TEST1 = 0x35 # Various Test Settings + self.DAVIS_TEST0 = 0x09 # Various Test Settings + + self.CC1101_IOCFG2 = 0x00 # GDO2 Output Pin Configuration + self.CC1101_IOCFG1 = 0x01 # GDO1 Output Pin Configuration + self.CC1101_IOCFG0 = 0x02 # GDO0 Output Pin Configuration + self.CC1101_FIFOTHR = 0x03 # RX FIFO and TX FIFO Thresholds + self.CC1101_SYNC1 = 0x04 # Sync Word, High Byte + self.CC1101_SYNC0 = 0x05 # Sync Word, Low Byte + self.CC1101_PKTLEN = 0x06 # Packet Length + self.CC1101_PKTCTRL1 = 0x07 # Packet Automation Control + self.CC1101_PKTCTRL0 = 0x08 # Packet Automation Control + self.CC1101_ADDR = 0x09 # Device Address + self.CC1101_CHANNR = 0x0A # Channel Number + self.CC1101_FSCTRL1 = 0x0B # Frequency Synthesizer Control + self.CC1101_FSCTRL0 = 0x0C # Frequency Synthesizer Control + self.CC1101_FREQ2 = 0x0D # Frequency Control Word, High Byte + self.CC1101_FREQ1 = 0x0E # Frequency Control Word, Middle Byte + self.CC1101_FREQ0 = 0x0F # Frequency Control Word, Low Byte + self.CC1101_MDMCFG4 = 0x10 # Modem Configuration + self.CC1101_MDMCFG3 = 0x11 # Modem Configuration + self.CC1101_MDMCFG2 = 0x12 # Modem Configuration + self.CC1101_MDMCFG1 = 0x13 # Modem Configuration + self.CC1101_MDMCFG0 = 0x14 # Modem Configuration + self.CC1101_DEVIATN = 0x15 # Modem Deviation Setting + self.CC1101_MCSM2 = 0x16 # Main Radio Control State Machine Configuration + self.CC1101_MCSM1 = 0x17 # Main Radio Control State Machine Configuration + self.CC1101_MCSM0 = 0x18 # Main Radio Control State Machine Configuration + self.CC1101_FOCCFG = 0x19 # Frequency Offset Compensation Configuration + self.CC1101_BSCFG = 0x1A # Bit Synchronization Configuration + self.CC1101_AGCCTRL2 = 0x1B # AGC Control + self.CC1101_AGCCTRL1 = 0x1C # AGC Control + self.CC1101_AGCCTRL0 = 0x1D # AGC Control + self.CC1101_WOREVT1 = 0x1E # High Byte Event0 Timeout + self.CC1101_WOREVT0 = 0x1F # Low Byte Event0 Timeout + self.CC1101_WORCTRL = 0x20 # Wake On Radio Control + self.CC1101_FREND1 = 0x21 # Front End RX Configuration + self.CC1101_FREND0 = 0x22 # Front End TX Configuration + self.CC1101_FSCAL3 = 0x23 # Frequency Synthesizer Calibration + self.CC1101_FSCAL2 = 0x24 # Frequency Synthesizer Calibration + self.CC1101_FSCAL1 = 0x25 # Frequency Synthesizer Calibration + self.CC1101_FSCAL0 = 0x26 # Frequency Synthesizer Calibration + self.CC1101_RCCTRL1 = 0x27 # RC Oscillator Configuration + self.CC1101_RCCTRL0 = 0x28 # RC Oscillator Configuration + self.CC1101_FSTEST = 0x29 # Frequency Synthesizer Calibration Control + self.CC1101_PTEST = 0x2A # Production Test + self.CC1101_AGCTEST = 0x2B # AGC Test + self.CC1101_TEST2 = 0x2C # Various Test Settings + self.CC1101_TEST1 = 0x2D # Various Test Settings + self.CC1101_TEST0 = 0x2E # Various Test Settings + + # CC1101 Status Registers. + self.CC1101_PARTNUM = 0x30 # Chip ID + self.CC1101_VERSION = 0x31 # Chip ID + self.CC1101_FREQEST = 0x32 # Frequency Offset Estimate from Demodulator + self.CC1101_LQI = 0x33 # Demodulator Estimate for Link Quality + self.CC1101_RSSI = 0x34 # Received Signal Strength Indication + self.CC1101_MARCSTATE = 0x35 # Main Radio Control State Machine State + self.CC1101_WORTIME1 = 0x36 # High Byte of WOR Time + self.CC1101_WORTIME0 = 0x37 # Low Byte of WOR Time + self.CC1101_PKTSTATUS = 0x38 # Current GDOx Status and Packet Status + self.CC1101_VCO_VC_DAC = 0x39 # Current Setting from PLL Calibration Module + self.CC1101_TXBYTES = 0x3A # Underflow and Number of Bytes + self.CC1101_RXBYTES = 0x3B # Overflow and Number of Bytes + self.CC1101_RCCTRL1_STATUS = 0x3C # Last RC Oscillator Calibration Result + self.CC1101_RCCTRL0_STATUS = 0x3D # Last RC Oscillator Calibration Result + + # CC1101 PA Table, TX FIFO and RX FIFO. + self.CC1101_PATABLE = 0x3E # PA TABLE address + self.CC1101_TXFIFO = 0x3F # TX FIFO address + self.CC1101_RXFIFO = 0x3F # RX FIFO address + + # CC1101 Command Strobes. + self.CC1101_SRES = 0x30 # Reset CC1101 chip + self.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). + self.CC1101_SXOFF = 0x32 # Turn off crystal oscillator + self.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) + self.CC1101_SRX = 0x34 # Enable RX. Perform calibration first if coming from IDLE and MCSM0.FS_AUTOCAL = 1 + self.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 + self.CC1101_SIDLE = 0x36 # Exit RX / TX, turn off frequency synthesizer and exit Wake-On-Radio mode if applicable + self.CC1101_SWOR = 0x38 # Start automatic RX polling sequence (Wake-on-Radio) if WORCTRL.RC_PD = 0 + self.CC1101_SPWD = 0x39 # Enter power down mode when CSn goes high + self.CC1101_SFRX = 0x3A # Flush the RX FIFO buffer. Only issue SFRX in IDLE or RXFIFO_OVERFLOW states + self.CC1101_SFTX = 0x3B # Flush the TX FIFO buffer. Only issue SFTX in IDLE or TXFIFO_UNDERFLOW states + self.CC1101_SWORRST = 0x3C # Reset real time clock to Event1 value + self.CC1101_SNOP = 0x3D # No operation. May be used to get access to the chip status byte + + # CC1101 Transfer Types. + self.WRITE_BURST = 0x40 + self.READ_SINGLE = 0x80 + self.READ_BURST = 0xC0 + + # CC1101 Returned Status Bytes. + self.CC1101_STATE_IDLE = 0x01 + self.CC1101_STATE_ENDCAL = 0x0C + self.CC1101_STATE_RX = 0x0D + self.CC1101_STATE_RXFIFO_ERROR = 0x11 + self.CC1101_STATE_TX = 0x13 + self.CC1101_STATE_TX_END = 0x14 + self.CC1101_STATE_TXFIFO_ERROR = 0x16 + self.reset() + self.setRegisters() + for i in range(0, 5): + self.freqComp[i] = self.DAVIS_FSCTRL0 + + def readRegister(self, regAddr): + addr = regAddr | self.READ_SINGLE + ss.off() + read_addr = bytes([addr]) + self.hspi.write(read_addr) + val = self.hspi.read(1, 0x00) + ss.on() + return int(val[0]) + + def writeRegister(self, regAddr, value): + ss.off() + self.hspi.write(bytes([regAddr])) + self.hspi.write(bytes([value])) + ss.on() + + def writeBurst(self, regAddr, wr_buffer): + addr = regAddr | self.WRITE_BURST + ss.off() + self.hspi.write(bytes([addr])) + for contents in wr_buffer: + self.hspi.write(bytes([contents])) + ss.on() + + def readBurst(self, regAddr, size): + addr = regAddr | self.READ_BURST + ss.off() + self.hspi.write(bytes([addr])) + rd_buffer = [] + for i in range(0, size): + rd_result = self.hspi.read(1, 0x00) + rd_buffer.append(hex(rd_result[0])) + ss.on() + return rd_buffer + + def cmdStrobe(self, command): + ss.off() + self.hspi.write(bytes([command])) + ss.on() + + def readStatus(self, regAddr): + addr = regAddr | self.READ_BURST + ss.off() + self.hspi.write(bytes([addr])) + value = self.hspi.read(1, 0x00) + ss.on() + return int(value[0]) + + def reset(self): + ss.off() + utime.sleep_ms(1) + ss.on() + utime.sleep_ms(1) + ss.off() + while so.value(): + pass + self.hspi.write(bytes([self.CC1101_SRES])) + while so.value(): + pass + ss.on() + + def sidle(self): + self.cmdStrobe(self.CC1101_SIDLE) + while self.readStatus(self.CC1101_MARCSTATE) != 0x01: + utime.sleep_us(100) + self.cmdStrobe(self.CC1101_SFTX) + self.cmdStrobe(self.CC1101_SFRX) + utime.sleep_us(100) + + def setRegisters(self): + self.writeRegister(self.CC1101_IOCFG2, self.DAVIS_IOCFG2) + self.writeRegister(self.CC1101_IOCFG1, self.DAVIS_IOCFG1) + self.writeRegister(self.CC1101_IOCFG0, self.DAVIS_IOCFG0) + self.writeRegister(self.CC1101_FIFOTHR, self.DAVIS_FIFOTHR) + self.writeRegister(self.CC1101_SYNC1, self.DAVIS_SYNC1) + self.writeRegister(self.CC1101_SYNC0, self.DAVIS_SYNC0) + self.writeRegister(self.CC1101_PKTLEN, self.DAVIS_PKTLEN) + self.writeRegister(self.CC1101_PKTCTRL1, self.DAVIS_PKTCTRL1) + self.writeRegister(self.CC1101_PKTCTRL0, self.DAVIS_PKTCTRL0) + self.writeRegister(self.CC1101_ADDR, self.DAVIS_ADDR) + self.writeRegister(self.CC1101_CHANNR, self.DAVIS_CHANNR) + self.writeRegister(self.CC1101_FSCTRL1, self.DAVIS_FSCTRL1) + self.writeRegister(self.CC1101_FSCTRL0, self.DAVIS_FSCTRL0) + self.writeRegister(self.CC1101_FREQ2, self.DAVIS_FREQ2) + self.writeRegister(self.CC1101_FREQ1, self.DAVIS_FREQ1) + self.writeRegister(self.CC1101_FREQ0, self.DAVIS_FREQ0) + self.writeRegister(self.CC1101_MDMCFG4, self.DAVIS_MDMCFG4) + self.writeRegister(self.CC1101_MDMCFG3, self.DAVIS_MDMCFG3) + self.writeRegister(self.CC1101_MDMCFG2, self.DAVIS_MDMCFG2) + self.writeRegister(self.CC1101_MDMCFG1, self.DAVIS_MDMCFG1) + self.writeRegister(self.CC1101_MDMCFG0, self.DAVIS_MDMCFG0) + self.writeRegister(self.CC1101_DEVIATN, self.DAVIS_DEVIATN) + self.writeRegister(self.CC1101_MCSM2, self.DAVIS_MCSM2) + self.writeRegister(self.CC1101_MCSM1, self.DAVIS_MCSM1) + self.writeRegister(self.CC1101_MCSM0, self.DAVIS_MCSM0) + self.writeRegister(self.CC1101_FOCCFG, self.DAVIS_FOCCFG) + self.writeRegister(self.CC1101_BSCFG, self.DAVIS_BSCFG) + self.writeRegister(self.CC1101_AGCCTRL2, self.DAVIS_AGCCTRL2) + self.writeRegister(self.CC1101_AGCCTRL1, self.DAVIS_AGCCTRL1) + self.writeRegister(self.CC1101_AGCCTRL0, self.DAVIS_AGCCTRL0) + self.writeRegister(self.CC1101_WOREVT1, self.DAVIS_WOREVT1) + self.writeRegister(self.CC1101_WOREVT0, self.DAVIS_WOREVT0) + self.writeRegister(self.CC1101_WORCTRL, self.DAVIS_WORCTRL) + self.writeRegister(self.CC1101_FREND1, self.DAVIS_FREND1) + self.writeRegister(self.CC1101_FREND0, self.DAVIS_FREND0) + self.writeRegister(self.CC1101_FSCAL3, self.DAVIS_FSCAL3) + self.writeRegister(self.CC1101_FSCAL2, self.DAVIS_FSCAL2) + self.writeRegister(self.CC1101_FSCAL1, self.DAVIS_FSCAL1) + self.writeRegister(self.CC1101_FSCAL0, self.DAVIS_FSCAL0) + self.writeRegister(self.CC1101_RCCTRL1, self.DAVIS_RCCTRL1) + self.writeRegister(self.CC1101_RCCTRL0, self.DAVIS_RCCTRL0) + self.writeRegister(self.CC1101_FSTEST, self.DAVIS_FSTEST) + self.writeRegister(self.CC1101_PTEST, self.DAVIS_PTEST) + self.writeRegister(self.CC1101_AGCTEST, self.DAVIS_AGCTEST) + self.writeRegister(self.CC1101_TEST2, self.DAVIS_TEST2) + self.writeRegister(self.CC1101_TEST1, self.DAVIS_TEST1) + self.writeRegister(self.CC1101_TEST0, self.DAVIS_TEST0) + self.writeBurst(self.CC1101_PATABLE, self.PA_TABLE) + self.setFrequency(self.hopIndex) + + def flush(self): + self.sidle() + self.cmdStrobe(self.CC1101_SFRX) # Flush Rx FIFO + self.cmdStrobe(self.CC1101_SFTX) # Flush Tx FIFO + self.rxBuffer = [0x00] * self.BUFFER_SIZE + self.rxBufferLength = self.rxBufferIndex = 0 + + def rx(self): + self.cmdStrobe(self.CC1101_SRX) + while (self.readStatus(self.CC1101_MARCSTATE) & 0x1F) != self.CC1101_STATE_RX: + utime.sleep_us(900) + self.rxing = 1 + self.writeRegister(self.CC1101_IOCFG0, self.DAVIS_IOCFG0) + + def hop(self): + self.hopIndex += 1 # Increment the index + if (self.hopIndex > 4): # 5 EU frequencies + self.hopIndex = 0 + self.writeRegister(self.CC1101_FSCTRL0, self.freqComp[self.hopIndex]) + self.setFrequency(self.hopIndex) # Set the frequency. + + def setFrequency(self, index): + self.sidle() + self.writeRegister(self.CC1101_FREQ2, self.FREQ_2[index]) + self.writeRegister(self.CC1101_FREQ1, self.FREQ_1[index]) + self.writeRegister(self.CC1101_FREQ0, self.FREQ_0[index]) + self.flush() + + def calcCrc(self, _buffer, length): + crc = 0x0000 + buffer_index = 0 + for i in range(length, 0, -1): + crc = (crc << 8) ^ self.CRC_TABLE[(crc >> 8) ^ (_buffer[buffer_index])] + buffer_index += 1 + return crc + + def readRssi(self): + rssi = self.readRegister(self.CC1101_RXFIFO) + if rssi >= 128: + return (rssi - 256)/2 - 74 + elif rssi < 128: + return rssi/2 -74 + else: + return False + + def readLQI(self): + return self.readRegister(self.CC1101_RXFIFO) & 0x7F + diff --git a/davis_decode.mpy b/davis_decode.mpy new file mode 100644 index 0000000000000000000000000000000000000000..d297958fbeec74dd196ec055c7a744153cee8d95 GIT binary patch literal 3139 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 4 + lsb = data & 0b00001111 + result = {"MSB": msb, "LSB": lsb} + return result + + def davis_id(self, header): + self.davis_packet_id = 0 + self.battery_low = 0 + self.unit_id = 0 + bin_header = self.byte_split(header) + self.unit_id = bin_header['LSB'] & 0b0111 + self.battery_low = bin_header['LSB'] >> 3 + self.davis_packet_id = bin_header['MSB'] + result = {"davis_id": self.unit_id, + "packet_id": self.davis_packet_id, + "bat_low": self.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 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 no rain + #print("b2:{} b3:{} = result:{}".format(byte2, byte3, byte2 + (byte3 >> 4 << 8))) + if byte2 == 255: + rainstate = 0 + rainrate = 0 + elif byte2 == 254: + rainstate = 1 + rainrate = 0.2 + else: + rainstate = 2 + if byte3 > 4: + rainrate = 720 / ((byte3 >> 4 << 8) + byte2) + else: + rainrate = 0 + result = {"state": float(rainstate), "rate": float(rainrate)} + #print(result) + return result + + def DecodePacket(self, packet): + # By default and most of the time, write to weather + self.write_influx_db = self.weather_influx_db + # Set all to None + self.wind = False + self.measurement = False + self.name = False + self.value = False + self.tags = False + self.wind = self.decode_wind( + {"windspeed": packet[1], "winddir": packet[2]}) + if self.davis_packet_id == 2: + # SuperCap charge 0x2 + if _DEBUG: + print('SCAP:') + supercap = self.supercap_decode( + packet[3], packet[4] + ) + if _DEBUG: + print("{}".format(supercap)) + self.write_influx_db = self.stat_influx_db + self.measurement = 'iss' + self.name = 'voltage' + self.tags = {'type': 'capacitor'} + self.value = supercap + elif self.davis_packet_id == 3: + # No fucking idea 0x3 + # {'hop':1,'h':48,'b0':6,'b1':237,'b2':255,'b3':195,'b4':135,'b5':50,'b6':110,'b7':255,'b8':255,'b9':179,'rssi':45,'lqi':0,'nxt':64,'cnt':163} + self.measurement = None + self.name = None + self.tags = None + self.value = None + + elif self.davis_packet_id == 5: + # Rainrate 0x5 + rainrate_dict = self.rainrate_decode( + packet[3], + packet[4]) + if _DEBUG: + print("RAINRATE: {}".format(rainrate_dict)) + self.measurement = 'rain' + self.name = 'value' + self.tags = {'type': 'rainrate'} + self.value = rainrate_dict['rate'] + elif self.davis_packet_id == 6: + # Sun Irradiation 0x6 (NOT ON vantage Vue) + pass + elif self.davis_packet_id == 7: + # Super Cap voltage 0x7 + solarvolt = self.solarvolt_decode( + packet[3], packet[4] + ) + if _DEBUG: + print("SOLV {}".format(solarvolt)) + self.write_influx_db = self.stat_influx_db + self.measurement = 'iss' + self.name = 'voltage' + self.tags = {'type': 'solar'} + self.value = solarvolt + elif self.davis_packet_id == 8: + # Temperature 0x8 + raw_temp = (packet[3] << 8) + packet[4] + temp_dict = self.decode_temp(raw_temp) + temp = float(temp_dict['celsius']) + if _DEBUG: + print("TEMP: {}".format(temp)) + self.measurement = 'temphumi' + self.name = 'temperature' + self.tags = {'type': 'external'} + self.value = temp + elif self.davis_packet_id == 9: + # Wind gusts 0x9 + windgust = packet[3] * 1.60934 + if _DEBUG: + print("WINDGUST: {}".format(windgust)) + self.measurement = 'wind' + self.name = 'value' + self.tags = {'type': 'windgust'} + self.value = windgust + elif self.davis_packet_id == 10: + # Humidity 0xa + raw_humidity = (((packet[4] >> 4) & 0b0011) << 8) \ + + packet[3] + humidity = round(int(raw_humidity) / float(10), 1) + if _DEBUG: + print("HUMI: {}".format(humidity)) + self.measurement = 'temphumi' + self.name = 'humidity' + self.tags = {'type': 'external'} + self.value = humidity + elif self.davis_packet_id == 14: + # Rain bucket tips 0xe + raw_rain = (packet[3]) + (packet[4] >> 7 << 8) + rain = self.rain_decode(raw_rain) + if _DEBUG: + print("RAINCOUNT: {}".format(rain)) + self.measurement = 'rain' + self.name = 'value' + self.tags = {'type': 'rain_bucket_tips'} + self.value = rain + diff --git a/inet.conf b/inet.conf new file mode 100644 index 0000000..8db3f0d --- /dev/null +++ b/inet.conf @@ -0,0 +1,20 @@ +# No spaces around '=' +# Try to avoid spaced at end or beginning of passwords. Stripping it +# One config option per line, all options need to be set. + +_SSID=YourSSID +_PASS=YourWifiPass + +# Time to keep waiting for the network to come up. Delay is 1001ms +_TIMEOUT=15 +_INFLUX_HOST=IPofTheInfluxDB +_INFLUX_PORT=PortOfTheInfluxDB + +# currently not implemented +_INFLUX_USER=InfluxUser +_INFLUX_PASS=HisPasswd + +# Names of the databases +_INF_DB_WEATHER=weather +_INF_DB_STATUS=status +_INF_DB_RAW=raw diff --git a/influx_structure.md b/influx_structure.md new file mode 100644 index 0000000..422fdee --- /dev/null +++ b/influx_structure.md @@ -0,0 +1,158 @@ +#Structure of InfluxDB +InfluxDB was chosen for it's simplicity and ease of management, live with it. + +##influxDB SCHEMA: +Firs off, 2 databases: + weather + status + +###DB weather + measure wind + ---------------- + value | speed or direction or windgust | davis_id + --------------------------------------------------- + field tag tag + + measure temphumi + ---------------- + temperature | humidity | external, internal | davis_id + --------------------------------------------------------- + field field tag tag + + measure rain + ---------------- + rain | rate / total / intensity | davis_id + --------------------------------------------- + field tag tag + + + +###DB status + + iss measure + ---------------- + voltage | solar or capacitor + ---------------------------------------------------------------- + field tag + + RasPI system (only on the raspi system) + ---------------- + usage | disk, mem, cpu, eth, wifi % + ------------------------------------ + field | tag + +## Retention policies + ------- + weather + ------- + create retention policy realtime on weather duration 168h replication 1 shard duration 1h + create retention policy monthly on weather duration 720h replication 1 shard duration 24h + create retention policy yearly on weather duration 8760h replication 1 shard duration 168h + create retention policy infinite on weather duration 0s replication 1 shard duration 720h + alter retention policy realtime on weather default + + ------ + status + ------ + create retention policy realtime on status duration 168h replication 1 shard duration 1h + create retention policy monthly on status duration 720h replication 1 shard duration 24h + create retention policy yearly on status duration 8760h replication 1 shard duration 168h + create retention policy infinite on status duration 0s replication 1 shard duration 720h + alter retention policy realtime on status default + +## Continuous queries + ------------------------------------------------------------------------------- + WIND + ------------------------------------------------------------------------------- + CREATE CONTINUOUS QUERY "cq_wind_10m" ON "weather_v2" BEGIN + SELECT max(value) AS wind_max, mean(value) AS wind, min(value) AS wind_min + INTO "monthly"."wind_aggregated" + FROM realtime.wind + GROUP BY time(10m), type + END + CREATE CONTINUOUS QUERY "cq_wind_1h" ON "weather_v2" BEGIN + SELECT max(value) AS wind_max, mean(value) AS wind, min(value) AS wind_min + INTO "yearly"."wind_aggregated" + FROM realtime.wind + GROUP BY time(1h), type + END + CREATE CONTINUOUS QUERY "cq_wind_6h" ON "weather_v2" BEGIN + SELECT max(value) AS wind_max, mean(value) AS wind, min(value) AS wind_min + INTO "infinite"."wind_aggregated" + FROM realtime.wind + GROUP BY time(6h), type + END + + ------------------------------------------------------------------------------- + RAIN + ------------------------------------------------------------------------------- + CREATE CONTINUOUS QUERY "cq_rain_10m" ON "weather_v2" BEGIN + SELECT max("value") AS val_max, mean(value) AS value + INTO "monthly"."rainrate_aggregated" + FROM realtime.rain + GROUP BY type,time(10m) + END + CREATE CONTINUOUS QUERY "cq_rain_1h" ON "weather_v2" BEGIN + SELECT max("value") AS val_max, mean(value) AS value + INTO "yearly"."rainrate_aggregated" + FROM realtime.rain + GROUP BY type,time(1h) + END + CREATE CONTINUOUS QUERY "cq_rain_6h" ON "weather_v2" BEGIN + SELECT max("value") AS val_max, mean(value) AS value + INTO "infinite"."rainrate_aggregated" + FROM realtime.rain + GROUP BY type,time(6h) + END + ------------------------------------------------------------------------------- + TEMPHUMI + ------------------------------------------------------------------------------- + CREATE CONTINUOUS QUERY "cq_temphumi_10m" ON "weather_v2" BEGIN + SELECT + max("humidity") AS humidity_max, + min("humidity") AS humidity_min, + mean("humidity") AS humidity, + max("temperature") AS temperature_max, + min("temperature") AS temperature_min, + mean("temperature") AS temperature + INTO "monthly"."temphumi_aggregated" + FROM realtime.temphumi + GROUP BY type, time(10m) + END + + CREATE CONTINUOUS QUERY "cq_temphumi_1h" ON "weather_v2" BEGIN + SELECT + max("humidity") AS humidity_max, + min("humidity") AS humidity_min, + mean("humidity") AS humidity, + max("temperature") AS temperature_max, + min("temperature") AS temperature_min, + mean("temperature") AS temperature + INTO "yearly"."temphumi_aggregated" + FROM realtime.temphumi + GROUP BY type, time(1h) + END + + CREATE CONTINUOUS QUERY "cq_temphumi_6h" ON "weather_v2" BEGIN + SELECT + max("humidity") AS humidity_max, + min("humidity") AS humidity_min, + mean("humidity") AS humidity, + max("temperature") AS temperature_max, + min("temperature") AS temperature_min, + mean("temperature") AS temperature + INTO "infinite"."temphumi_aggregated" + FROM realtime.temphumi + GROUP BY type, time(6h) + END + + ------------------------------------------------------------------------------- + TRAFFIC + ------------------------------------------------------------------------------- + CREATE CONTINUOUS QUERY "cq_net_1m" ON "status" BEGIN + SELECT NON_NEGATIVE_DERIVATIVE(max(*)) as traffic + INTO "monthly"."net_aggregated" + FROM realtime.net + WHERE time > now()-1m + GROUP BY time(30s) + END diff --git a/main.py b/main.py new file mode 100644 index 0000000..a091243 --- /dev/null +++ b/main.py @@ -0,0 +1,89 @@ +import cc1101_davis +import davis_decode +import utime +import WiFi +gc.collect() + +_DEBUG = False + +wifi_con = WiFi.NetSet('infra') +wifi_con.readNetworkConfig() +ips = wifi_con.connectInfra( + wifi_con._SSID, + wifi_con._PASS, + wifi_con._TIMEOUT) + +if _DEBUG: + print("IPCONF: {}".format(ips)) + +davis = cc1101_davis.CC1101() +davis.setRegisters() +davis.setFrequency(davis.hopIndex) +decoder = davis_decode.davisDecoder( + wifi_con._INF_DB_WEATHER, + wifi_con._INF_DB_STATUS, + wifi_con._INF_DB_RAW) + +# Main receive loop +while True: + data_length = davis.readRegister(davis.CC1101_RXBYTES) + data = "" + if data_length & 0x7f == 15: + data = davis.readBurst(davis.CC1101_RXFIFO, 10) + rssi = davis.readRssi() + lqi = davis.readLQI() + hop = davis.hopIndex + davis.flush() + davis.hop() + davis.rx() + data_int = [davis_decode.reverseBits(int(item)) for item in data] + header = decoder.davis_id(data_int[0]) + decoder.DecodePacket(data_int) + data_prn = "{:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5}".format( + data_int[0], + data_int[1], + data_int[2], + data_int[3], + data_int[4], + data_int[5], + data_int[6], + data_int[7], + data_int[8], + data_int[8],) + print("{_data:60} HOP: {_hop:<5} RSSI: {_rssi:<5} LQI: {_lqi:<5}".format( + _rssi=rssi, + _hop=hop, + _data=data_prn, + _lqi=lqi)) + if _DEBUG: + print("Header: {} Wind: {}".format(header, decoder.wind)) + print("{}: {}/{} ({})".format( + decoder.measurement, + decoder.name, + decoder.value, + decoder.tags)) + sent_ok = False + data_sent = None + try: + (sent_ok, data_sent) = davis_decode.send_to_influx( + wifi_con._INFLUX_HOST, + wifi_con._INFLUX_PORT, + decoder.write_influx_db, + wifi_con._INFLUX_USER, + wifi_con._INFLUX_PASS, + decoder.unit_id, + decoder.wind, + decoder.measurement, + decoder.name, + decoder.value, + decoder.tags) + except Exception as e: + print("ERROR: Data send 'urequest': {}".format(e)) + if _DEBUG: + if sent_ok: + print("DATA SEND: {}".format(data_sent.status_code)) + else: + print("DATA SEND FAIL: {}".format(data_sent)) + else: + utime.sleep_ms(100) + gc.collect() diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..dc1b8bb --- /dev/null +++ b/readme.md @@ -0,0 +1,371 @@ +#ESP8266 + CC1101 Davis weather station wifi logger + + +The intention is to gather data from a Weather station made by Davis. +In this case a Vantage Vue (integrated all sensors in one package) and +push them directly to an InfluxDB instance via WiFi. + +##Prerequisites: +- WiFi available, plus access to it. (WPA2 PSK preferred) +- Running INfluxDB instance on the same network +- ESP8266 microcontroller (NodeMCU 8266 preferred) +- git, since you're here, I assume this is already settled +- Micropython tools: ampy, esptool +- serial console emulator, I use picocom +- CC1101 from Texas Instruments radio chip, the 868 MHz version! There are 433Mhz sold out there, read descriptions +- a couple wires, need to solder those tiny CC1101 pins and wires. +- 5V / 3.3V source, depending on what version of ESP you get. A stable 3.3V is preferred + +##Quick setup of HW: +###1. Interconnect these pins: +The pins are marked on the NodeMcu ESP8266, on the CC1101 module, pins are counted from left, if you face the chip towards you. + +ESP8266 | ESP8266 description | CC1101 | CC1101 description | +|-------|---------------------|-----------|--------------------| +|GND | GND | 1- GND | GND | +|3V3 | Voltage In | 2- VCC | Vcc, Input voltage | +|D7 | GPIO13 | 3- Pin3 | MOSI | +|D5 | GPIO14 | 4- Pin4 | SCLK | +|D6 | GPIO12 | 5- Pin5 | MISO | +|- | - | 6- Pin6 | GDO2 | +|- | - | 7- Pin7 | GDO0 | +|D8 | GPIO15 | 8- Pin8 | CSN | + +IO pin GDO0, currently configured as interrupt, when new packet is received (i.e. goes HIGH). +This currently just to lights up a led, interrupt based receive is not implemented, though sounds cool. + +###2. Clone this repo, get Micropython +``` +git clone https://bastart.spoton.cz/git/Ventil/esp8266_CC1101_davis_vantage_vue.git +cd esp8266_CC1101_davis_vantage_vue +wget https://micropython.org/resources/firmware/esp8266-20191220-v1.12.bin -P /tmp +``` +###3. Upload Micropython, check +Need to delete the flash first, then write the custom firmware. +You need to determine which device is the one +to write to. If you have only one ESP8266, you are probably safe to use the ttyUSB0, +I will use ttyUSB1 as I have multiple UARTS on my machine +``` +esptool.py --port /dev/ttyUSB1 --baud 115200 erase_flash +esptool.py v2.8 +Serial port /dev/ttyUSB1 +Connecting.... +Detecting chip type... ESP8266 +Chip is ESP8266EX +Features: WiFi +Crystal is 26MHz +MAC: cc:50:e3:56:b2:5b +Uploading stub... +Running stub... +Stub running... +Erasing flash (this may take a while)... +Chip erase completed successfully in 7.8s +Hard resetting via RTS pin... +``` +Now it is time to upload uPython: +``` +esptool.py --port /dev/ttyUSB1 --baud 115200 write_flash 0 /tmp/esp8266-20191220-v1.12.bin +esptool.py v2.8 +Serial port /dev/ttyUSB1 +Connecting.... +Detecting chip type... ESP8266 +Chip is ESP8266EX +Features: WiFi +Crystal is 26MHz +MAC: cc:50:e3:56:b2:5b +Uploading stub... +Running stub... +Stub running... +Configuring flash size... +Auto-detected Flash size: 4MB +Flash params set to 0x0040 +Compressed 619828 bytes to 404070... +Wrote 619828 bytes (404070 compressed) at 0x00000000 in 35.8 seconds (effective 138.6 kbit/s)... +Hash of data verified. + +Leaving... +Hard resetting via RTS pin... +``` +And of course, when you try to get to the serial console, you should get somethinglike this +``` +picocom -b 115200 /dev/ttyUSB1 +picocom v3.1 + +port is : /dev/ttyUSB1 +flowcontrol : none +baudrate is : 115200 +parity is : none +databits are : 8 +stopbits are : 1 +escape is : C-a +local echo is : no +noinit is : no +noreset is : no +hangup is : no +nolock is : no +send_cmd is : sz -vv +receive_cmd is : rz -vv -E +imap is : +omap is : +emap is : crcrlf,delbs, +logfile is : none +initstring : none +exit_after is : not set +exit is : no + +Type [C-a] [C-h] to see available commands +Terminal ready + +>>> + +``` +Exit from the console with Ctrl + A + X or ampy will not be able to list files +on the esp8266 +###4. Freeze modules, or just upload the .mpy files +You can simply upload all the modules in question. If you modify anything in the .py +files, you need to freeze them again with mpy-cross +``` +/usr/bin/ampy -p /dev/ttyUSB1 put WiFi.mpy +/usr/bin/ampy -p /dev/ttyUSB1 put cc1101_davis.mpy +/usr/bin/ampy -p /dev/ttyUSB1 put davis_decode.mpy +``` + +to freeze a module: +``` +mpy-cross WiFi.py +ls -l WiFi.* +WiFi.mpy <-- the newly compiled (frozen) module +WiFi.py + +``` +###5. Modify inet.conf +Before uploading the inte.conf, please change it to your desired values. +###6, Upload files +``` +/usr/bin/ampy -p /dev/ttyUSB1 put boot.py +/usr/bin/ampy -p /dev/ttyUSB1 put main.py +/usr/bin/ampy -p /dev/ttyUSB1 put inet.conf + +/usr/bin/ampy -p /dev/ttyUSB1 ls +/WiFi.mpy +/boot.py +/cc1101_davis.mpy +/davis_decode.mpy +/inet.conf +/main.py + +``` +###7. If you haven't already, create 2 DBs in inclux +I am tempted to push the raw, undecoded data to a DB as well, but influx is not siutd for this. You can ignore the last DB creation +``` +ssh 192.168.1.2 +influx +create database weather +create database status +create database raw +``` +###8. Restart esp8266, check data on serial +``` +picocom -b 115200 /dev/ttyUSB1 +picocom v3.1 + +port is : /dev/ttyUSB1 +flowcontrol : none +baudrate is : 115200 +parity is : none +databits are : 8 +stopbits are : 1 +escape is : C-a +local echo is : no +noinit is : no +noreset is : no +hangup is : no +nolock is : no +send_cmd is : sz -vv +receive_cmd is : rz -vv -E +imap is : +omap is : +emap is : crcrlf,delbs, +logfile is : none +initstring : none +exit_after is : not set +exit is : no + +Type [C-a] [C-h] to see available commands +Terminal ready + +>>> +>>> +>>> +>>> +>>> +>>> +>>> +>>> +>>> +>>> +>>> +>>> {ll��|�#�o + + � + $� + c|����|#� + #��nN�$oN��� + bp��$sl{lp�o� + �l + + + " + N�|�l + + #��nN�$��l`�Nl or���N + + � + �$p�n� + r�ܜ� + + bn�|$ + �� + #��on� + l �n$�$`n{�ےo + l �o + + ��#�ol� + ��no��{lp�o� + + r����� + �p� + # + N�|��p��on�l�l �n$�$`nr���� + $l`{��n + l$ ���o�r��n|�ll$d`#���r�l�o��o�l ��r�$�$� + l`��r�p��l� + + $`���o�l��� + l$`sl��b���# + ��B| + $b���#|����l$b��n��Trying... 15 more times +Trying... 14 more times +Trying... 13 more times +Trying... 12 more times +Trying... 11 more times +Trying... 10 more times +IPCONF: {'ip': '192.168.1.174', 'mask': '255.255.255.0', 'gw': '192.168.1.254', 'dns': '192.168.1.4'} + 0 10 4 35 8 4 78 196 255 255 HOP: 0 RSSI: -71.5 LQI: 127 +Header: {'bat_low': 0, 'packet_id': 0, 'davis_id': 0} Wind: {'speed': 16.1, 'direction': 5.6} +False: False/False (False) +DATA SEND FAIL: ERROR measurement set False + 224 10 234 17 1 4 38 49 255 255 HOP: 1 RSSI: -70 LQI: 127 +Header: {'bat_low': 0, 'packet_id': 14, 'davis_id': 0} Wind: {'speed': 16.1, 'direction': 327.6} +rain: value/17.0 ({'type': 'rain_bucket_tips'}) +DATA SEND: 204 + 80 9 224 255 113 15 89 230 255 255 HOP: 2 RSSI: -70 LQI: 127 +Header: {'bat_low': 0, 'packet_id': 5, 'davis_id': 0} Wind: {'speed': 14.5, 'direction': 313.6} +rain: value/0.0 ({'type': 'rainrate'}) +DATA SEND: 204 + 128 8 252 34 233 10 229 180 255 255 HOP: 3 RSSI: -70 LQI: 127 +Header: {'bat_low': 0, 'packet_id': 8, 'davis_id': 0} Wind: {'speed': 12.9, 'direction': 352.8} +temphumi: temperature/13.3 ({'type': 'external'}) +DATA SEND: 204 + 160 8 249 109 41 2 202 57 255 255 HOP: 4 RSSI: -69 LQI: 127 +Header: {'bat_low': 0, 'packet_id': 10, 'davis_id': 0} Wind: {'speed': 12.9, 'direction': 348.6} +temphumi: humidity/62.1 ({'type': 'external'}) +DATA SEND: 204 + 224 9 225 17 1 2 182 58 255 255 HOP: 0 RSSI: -68 LQI: 127 +Header: {'bat_low': 0, 'packet_id': 14, 'davis_id': 0} Wind: {'speed': 14.5, 'direction': 315.0} +rain: value/17.0 ({'type': 'rain_bucket_tips'}) +DATA SEND: 204 + 80 10 241 255 115 10 236 224 255 255 HOP: 1 RSSI: -68.5 LQI: 127 +Header: {'bat_low': 0, 'packet_id': 5, 'davis_id': 0} Wind: {'speed': 16.1, 'direction': 337.4} +rain: value/0.0 ({'type': 'rainrate'}) +DATA SEND: 204 + 128 16 233 34 219 10 39 214 255 255 HOP: 2 RSSI: -68.5 LQI: 127 +Header: {'bat_low': 0, 'packet_id': 8, 'davis_id': 0} Wind: {'speed': 25.7, 'direction': 326.2} +temphumi: temperature/13.2 ({'type': 'external'}) +``` +And with '_DEBUG' set to False on line 7, in main.py: +You can see the raw data that are comming in from the Davis weather station. + +``` +>>> +MPY: soft reboot +Trying... 15 more times + 0 17 244 212 193 138 98 76 255 255 HOP: 0 RSSI: -68 LQI: 127 + 224 13 3 17 3 3 32 253 255 255 HOP: 1 RSSI: -68.5 LQI: 127 + 80 13 5 255 112 2 10 211 255 255 HOP: 2 RSSI: -68 LQI: 127 + 128 9 236 34 203 2 181 206 255 255 HOP: 3 RSSI: -68.5 LQI: 127 + 160 11 252 106 43 2 123 92 255 255 HOP: 4 RSSI: -69 LQI: 127 + 224 13 247 17 3 6 37 228 255 255 HOP: 0 RSSI: -68.5 LQI: 127 + 80 12 6 255 113 2 8 111 255 255 HOP: 1 RSSI: -69 LQI: 127 + 128 9 239 34 203 3 62 51 255 255 HOP: 2 RSSI: -70 LQI: 127 + 32 8 232 212 195 128 93 215 255 255 HOP: 3 RSSI: -70.5 LQI: 127 + 224 6 239 17 3 3 7 218 255 255 HOP: 4 RSSI: -69.5 LQI: 127 + 80 7 237 255 115 7 72 162 255 255 HOP: 0 RSSI: -69 LQI: 127 + 128 9 14 34 203 0 252 14 255 255 HOP: 1 RSSI: -68.5 LQI: 127 +``` + +If you are interested in the packet format, please: +``` + Header byte0 byte1 byte2 byte3 byte4 byte5 byte6 byte7 byte8 Freq Sig_strength Link_quality + 224 13 3 17 3 3 32 253 255 255 HOP: 1 RSSI: -68.5 LQI: 127 +``` + +###9, explore data in Influx +You're all set, let's look at the data +``` +ssh 192.168.1.2 +influx +> use weather +Using database weather +> select * from wind where time > now() - 1m group by type +name: wind +tags: type=direction +time davis_id value +---- -------- ----- +1590755264047546640 0 23.8 +1590755266495686261 0 12.6 +1590755269170611760 0 315 +1590755271670765583 0 4.2 +1590755274297615725 0 340.2 +1590755276695612090 0 351.4 +1590755279347820779 0 14 +1590755282048791933 0 14 +1590755284420807397 0 5.6 +1590755287147874950 0 5.6 +1590755289521471177 0 25.2 +1590755292271754999 0 323.4 +1590755294748104920 0 351.4 +1590755297195883784 0 350 +1590755302322174318 0 330.4 +1590755317723159515 0 333.2 +1590755320496027169 0 341.6 + +name: wind +tags: type=speed +time davis_id value +---- -------- ----- +1590755264047546640 0 20.9 +1590755266495686261 0 20.9 +1590755269170611760 0 16.1 +1590755271670765583 0 25.7 +1590755274297615725 0 24.1 +1590755276695612090 0 22.5 +1590755279347820779 0 24.1 +1590755282048791933 0 22.5 +1590755284420807397 0 20.9 +1590755287147874950 0 17.7 +1590755289521471177 0 20.9 +1590755292271754999 0 22.5 +1590755294748104920 0 19.3 +1590755297195883784 0 22.5 +1590755302322174318 0 20.9 +1590755317723159515 0 11.3 +1590755320496027169 0 12.9 + +name: wind +tags: type=windgust +time davis_id value +---- -------- ----- +1590755279347820779 0 25.7494 +``` +### Optionally, get grafana to plot the graphs for you