├── BCH3121.cpp ├── BCH3121.h ├── CLI.h ├── Flash.h ├── LICENSE ├── Log.h ├── README.md ├── SX1278.h ├── SX1278ISR.h ├── TELNET.h ├── TFT.h ├── WLAN.h ├── documentation ├── AN142(POCSAG).pdf ├── SX127x Datasheet.pdf ├── TR-BOS_Digitale-Funkalarmierung-April-2011_002.pdf ├── console.png └── pocsagRX.png └── pocsagRX.ino /BCH3121.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 by Andy Uribe CA6JAU 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "BCH3121.h" 20 | 21 | /* 22 | * This is a BCH(31,21) implementation, designed for POCSAG 23 | * Based on double-error-correcting method for binary BCH codes 24 | * 25 | * More info (math base): 26 | * https://web.stanford.edu/class/ee387/handouts/notes16.pdf 27 | * https://www.ece.jhu.edu/~cooper/ERROR_CONTROL_CODING/06dec.pdf 28 | */ 29 | 30 | // Polynomial form of alpha^i, GF(2^5), using irreducible polynomial: p(X) = X^5 + X^2 + 1 31 | // See http://pnrsolution.org/Datacenter/Vol3/Issue2/250.pdf (Table I) 32 | const int8_t alpha_to[] = { 1, 2, 4, 8, 16, 5, 10, 20, 13, 26, 33 | 17, 7, 14, 28, 29, 31, 27, 19, 3, 6, 34 | 12, 24, 21, 15, 30, 25, 23, 11, 22, 9, 35 | 18, 0}; 36 | 37 | // Index of alpha^i: 38 | const int8_t index_of[] = {-1, 0, 1, 18, 2, 5, 19, 11, 3, 29, 39 | 6, 27, 20, 8, 12, 23, 4, 10, 30, 17, 40 | 7, 22, 28, 26, 21, 25, 9, 16, 13, 14, 41 | 24, 15}; 42 | 43 | #define POCSAG_DATA_MASK 0xFFFFF800; 44 | #define POCSAG_BCH_N 31 45 | #define POCSAG_BCH_K 21 46 | 47 | // Polynomial generator g(X) = X^10 + X^9 + X^8 + X^6 + X^5 + X3 + 1 48 | #define POCSAG_BCH_GENPOLY 0x00000769U 49 | 50 | CBCH3121::CBCH3121() : 51 | m_S1(0U), 52 | m_S3(0U) 53 | { 54 | } 55 | 56 | void CBCH3121::encode(uint32_t& data) 57 | { 58 | // Just use data part of the codeword 59 | data &= POCSAG_DATA_MASK; 60 | 61 | uint32_t tmp = data; 62 | uint32_t gen_poly = POCSAG_BCH_GENPOLY << POCSAG_BCH_K; 63 | 64 | // Calculate BCH check bits 65 | for (unsigned int i = 0U; i < POCSAG_BCH_K; i++, tmp <<= 1) { 66 | if (tmp & 0x80000000U) 67 | tmp ^= gen_poly; 68 | } 69 | 70 | // Add BCH check bits 71 | data |= (tmp >> POCSAG_BCH_K); 72 | 73 | // Add parity bit 74 | if (calc_parity(data)) 75 | data |= 0x01U; 76 | } 77 | 78 | errors CBCH3121::decode(uint32_t& data) 79 | { 80 | int8_t S1_3, tmp, X1, X2; 81 | uint8_t cnt = 0U; 82 | uint8_t Q = 0U; 83 | uint8_t locator[5U]; 84 | 85 | // Calculate syndrome 86 | calc_syndrome(data); 87 | 88 | if (m_S1 == -1 && m_S3 == -1) { 89 | // return if no errors 90 | return {0,check_parity(data)}; 91 | } 92 | else { 93 | // Calculate S1^3 in GF(2^5): 94 | S1_3 = (m_S1 * 3) % POCSAG_BCH_N; 95 | 96 | // Check for single error 97 | if (S1_3 == m_S3) { 98 | // Correct single error 99 | data ^= (0x01U << (m_S1 + 1U)); 100 | return {1,check_parity(data)}; 101 | } 102 | else { // More than 1 errors 103 | 104 | // Calculate in GF(2^5): 105 | // X1 = S1 106 | // X2 = (S3 + S1^3) / S1 107 | 108 | X1 = m_S1; 109 | 110 | if (m_S3 != -1) 111 | tmp = alpha_to[S1_3] ^ alpha_to[m_S3]; 112 | else 113 | tmp = alpha_to[S1_3]; 114 | 115 | X2 = (index_of[tmp] - m_S1 + POCSAG_BCH_N) % POCSAG_BCH_N; 116 | 117 | // Chien search 118 | for (uint8_t i = 1U; i <= POCSAG_BCH_N; i++) { 119 | Q = 1U; 120 | 121 | if (X1 != -1) { 122 | X1 = (X1 + 1) % POCSAG_BCH_N; 123 | Q ^= alpha_to[X1]; 124 | } 125 | 126 | if (X2 != -1) { 127 | X2 = (X2 + 2) % POCSAG_BCH_N; 128 | Q ^= alpha_to[X2]; 129 | } 130 | 131 | if (!Q) { 132 | locator[cnt] = i % POCSAG_BCH_N; 133 | cnt++; 134 | } 135 | } 136 | 137 | if (cnt != 2U) { // Decoding failed, too many errors 138 | return {2,1}; 139 | } 140 | 141 | // Correct double error: 142 | data ^= (0x80000000U >> (locator[0U] - 1U)); 143 | data ^= (0x80000000U >> (locator[1U] - 1U)); 144 | return {2,check_parity(data)}; 145 | } 146 | } 147 | 148 | return {0,1}; 149 | } 150 | 151 | void CBCH3121::calc_syndrome(uint32_t data) 152 | { 153 | // Reset syndromes 154 | m_S1 = 0; 155 | m_S3 = 0; 156 | 157 | // Drop parity bit 158 | data >>= 1; 159 | 160 | // Calculate just syndromes S1 and S3 161 | for (uint8_t i = 0; i < POCSAG_BCH_N; i++, data >>= 1) { 162 | if (data & 0x01U) { 163 | m_S1 ^= alpha_to[i % POCSAG_BCH_N]; 164 | m_S3 ^= alpha_to[(3 * i) % POCSAG_BCH_N]; 165 | } 166 | } 167 | 168 | // Transform polynomial form to index form 169 | m_S1 = index_of[m_S1]; 170 | m_S3 = index_of[m_S3]; 171 | } 172 | 173 | bool CBCH3121::calc_parity(uint32_t data) 174 | { 175 | data ^= data >> 16; 176 | data ^= data >> 8; 177 | data ^= data >> 4; 178 | data ^= data >> 2; 179 | data ^= data >> 1; 180 | 181 | return data & 0x01U; 182 | } 183 | 184 | uint8_t CBCH3121::check_parity(uint32_t& data) 185 | { 186 | // Check for error in parity bit 187 | if (calc_parity(data)) { 188 | data ^= 0x01; 189 | return 1U; 190 | } else 191 | return 0U; 192 | } 193 | -------------------------------------------------------------------------------- /BCH3121.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 by Andy Uribe CA6JAU 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef BCH3121_H 20 | #define BCH3121_H 21 | 22 | #include 23 | 24 | struct errors { uint8_t corrected; uint8_t uncorrected; }; 25 | 26 | class CBCH3121 { 27 | public: 28 | CBCH3121(); 29 | 30 | void encode(uint32_t& data); 31 | errors decode(uint32_t& data); 32 | 33 | private: 34 | void calc_syndrome(uint32_t data); 35 | bool calc_parity(uint32_t data); 36 | uint8_t check_parity(uint32_t& data); 37 | 38 | int8_t m_S1; 39 | int8_t m_S3; 40 | 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /CLI.h: -------------------------------------------------------------------------------- 1 | #include "Flash.h" 2 | 3 | String cmdLine=""; 4 | 5 | void help() { 6 | Log.print(0,"debug [0-3]\r\n"); 7 | Log.print(0,"monitor\r\n"); 8 | Log.print(0,"get version\r\n"); 9 | Log.print(0,"get status\r\n"); 10 | Log.print(0,"clear status\r\n"); 11 | Log.print(0,"get configuration\r\n"); 12 | Log.print(0,"get register\r\n"); 13 | Log.print(0,"set frequency [137.000-525.000]\r\n"); 14 | Log.print(0,"set offset [0-100|auto]\r\n"); 15 | Log.print(0,"set bitrate [0.075-250]\r\n"); 16 | Log.print(0,"set shift [0.6-200]\r\n"); 17 | Log.print(0,"set rxbw [2.6-250|auto]\r\n"); 18 | Log.print(0,"set afcbw [2.6-250|auto]\r\n"); 19 | Log.print(0,"set bos\r\n"); 20 | Log.print(0,"set daufilter [Filter]\r\n"); 21 | Log.print(0,"set ssid [SSID]\r\n"); 22 | Log.print(0,"set secret [Secret]\r\n"); 23 | Log.print(0,"set gwurl [https://foo.de/foo]\r\n"); 24 | Log.print(0,"set rawurl [https://foo.de/foo]\r\n"); 25 | Log.print(0,"set password [Password]\r\n"); 26 | Log.print(0,"connect wlan\r\n"); 27 | Log.print(0,"clear wlan\r\n"); 28 | Log.print(0,"restart rx\r\n"); 29 | Log.print(0,"restart cpu\r\n"); 30 | Log.print(0,"get flash\r\n"); 31 | Log.print(0,"read flash\r\n"); 32 | Log.print(0,"write flash\r\n"); 33 | Log.print(0,"erase flash\r\n"); 34 | Log.print(0,"exit\r\n"); 35 | Log.print(0,"help\r\n"); } 36 | 37 | void doParse() { 38 | cmdLine.trim(); 39 | if (cmdLine!="") { Log.print(0,"\r\n"); } 40 | String value=cmdLine.substring(cmdLine.lastIndexOf(" ")+1); 41 | if (cmdLine.startsWith("deb")) { Log.debug=value.toInt(); Log.print(0,"Debug Level: %i\r\n",Log.debug); } 42 | else if (cmdLine.startsWith("mon")) { modem.monitorRx=!modem.monitorRx; } 43 | else if (cmdLine.startsWith("get ver")) { modem.printChip(); } 44 | else if (cmdLine.startsWith("get stat")) { modem.printRx(); 45 | Log.print(0,"Messages received: %i",modem.messageCount); 46 | Log.print(0," Errors corrected: %i",modem.errorCount.corrected); 47 | Log.print(0," uncorrected: %i",modem.errorCount.uncorrected); 48 | Log.print(0," Bytes queued: %i/%i\r\n",uxQueueMessagesWaitingFromISR(queueDIO1),queueSizeDIO1); 49 | Log.print(0,"WLAN Status: "); if (WiFi.status()==3) { Log.print(0,"Up (%i)",WiFi.status()); } else { Log.print(0,"Down (%i)",WiFi.status()); } 50 | if (hasIP) { Log.print(0," IP: %s",WiFi.localIP().toString().c_str()); } 51 | Log.print(0," RSSI: %i dBm",WiFi.RSSI()); 52 | Log.print(0," Events Up: %i",upEvents); 53 | Log.print(0," Down: %i\r\n",downEvents); 54 | Log.print(0,"HTTP Status: %i",httpStatus); if (httpStatus==200) { Log.print(0," OK"); } 55 | Log.print(0," Retried: %i",httpRetried); 56 | Log.print(0," Failed: %i\r\n",httpFailed); 57 | Log.print(0,"TELNET Session: %i",sessionActive); 58 | if (sessionActive) { Log.print(0," IP: %s",telnetSession.remoteIP().toString().c_str()); } 59 | if (sessionActive) { Log.print(0," Auth: %i",isAuth); } 60 | Log.print(0," Monitor: %i",modem.monitorRx); 61 | Log.print(0," Debug: %i\r\n",Log.debug); 62 | Log.print(0,"Uptime: %i days %s hours\r\n",modem.upTime/86400,String((double)(modem.upTime%86400)/3600.0,2).c_str()); } 63 | else if (cmdLine.startsWith("clear stat")) { modem.messageCount=0; modem.errorCount.corrected=0; modem.errorCount.uncorrected=0; upEvents=0; downEvents=0; httpRetried=0; httpFailed=0; Log.print(0,"Statistics cleared\r\n"); } 64 | else if (cmdLine.startsWith("get conf")) { 65 | Log.print(0,"Center Frequency: %s MHz\r\n",String(modem.centerFreq,5).c_str()); 66 | Log.print(0,"Rx Frequency Offset: %s kHz\r\n",String(modem.rxOffset,3).c_str()); 67 | Log.print(0,"Bitrate: %s bps\r\n",String(modem.bitrate*1000,0).c_str()); 68 | Log.print(0,"Shift Frequency: +/- %s Hz\r\n",String(modem.shift*1000,0).c_str()); 69 | Log.print(0,"Rx Bandwidth: %s kHz\r\n",String(modem.rxBandwidth,1).c_str()); 70 | Log.print(0,"AFC Bandwidth: %s kHz\r\n",String(modem.afcBandwidth,1).c_str()); 71 | Log.print(0,"BOS Mode: %i\r\n",modem.isBOS); 72 | Log.print(0,"DAU Filter: %s\r\n",String(modem.daufilter).c_str()); 73 | Log.print(0,"WLAN SSID: %s\r\n",String(wlanSSID).c_str()); 74 | if (wlanSecret!="") { Log.print(0,"WLAN Secret: xxxx\r\n"); } else { Log.print(0,"WLAN Secret:\r\n"); } 75 | Log.print(0,"Gateway URL: %s\r\n",String(gwURL).c_str()); 76 | Log.print(0,"Raw URL: %s\r\n",String(rawURL).c_str()); 77 | Log.print(0,"TELNET Password: xxxx\r\n"); } 78 | else if (cmdLine.startsWith("get reg")) { modem.regDump(); } 79 | else if (cmdLine.startsWith("set freq")) { modem.stopSequencer(); modem.setFrequency(value.toDouble()); modem.startSequencer(); modem.restartRx(true); } 80 | else if (cmdLine.startsWith("set offset")) { if (value!="auto") { modem.rxOffset=value.toDouble(); } 81 | modem.stopSequencer(); modem.setFrequency(modem.centerFreq,modem.rxOffset); modem.startSequencer(); modem.restartRx(true); } 82 | else if (cmdLine.startsWith("set bitrate")) { modem.setBitrate(value.toDouble()); } 83 | else if (cmdLine.startsWith("set shift")) { modem.stopSequencer(); modem.setShift(value.toDouble()); modem.startSequencer(); modem.restartRx(true); } 84 | else if (cmdLine.startsWith("set rxbw")) { if (value=="auto") { modem.setRxBwAuto(); } else { modem.setRxBandwidth(value.toDouble()); } } 85 | else if (cmdLine.startsWith("set afcbw")) { if (value=="auto") { modem.setAfcBwAuto(); } else { modem.setAfcBandwidth(value.toDouble()); } } 86 | else if (cmdLine.startsWith("set bos")) { modem.isBOS=!modem.isBOS; Log.print(0,"BOS Mode: %i\r\n",modem.isBOS); } 87 | else if (cmdLine.startsWith("set dau")) { if (value=="dau" || value=="daufilter") { modem.daufilter=""; } else { modem.daufilter=value; } Log.print(0,"DAU Filter: %s\r\n",String(modem.daufilter).c_str()); } 88 | else if (cmdLine.startsWith("set ssid")) { if (value=="ssid") { wlanSSID=""; } else { wlanSSID=value; } Log.print(0,"WLAN SSID: %s\r\n",String(wlanSSID).c_str()); } 89 | else if (cmdLine.startsWith("set secret")) { if (value=="secret") { wlanSecret=""; } else { wlanSecret=value; } if (wlanSecret!="") { Log.print(0,"WLAN Secret: xxxx\r\n"); } else { Log.print(0,"WLAN Secret:\r\n"); } } 90 | else if (cmdLine.startsWith("set gw")) { if (value=="gw" || value=="gwurl") { gwURL=""; } else { gwURL=value; } Log.print(0,"Gateway URL: %s\r\n",String(gwURL).c_str()); } 91 | else if (cmdLine.startsWith("set raw")) { if (value=="raw" || value=="rawurl") { rawURL=""; } else { rawURL=value; } Log.print(0,"Raw URL: %s\r\n",String(rawURL).c_str()); } 92 | else if (cmdLine.startsWith("set pass")) { if (value=="pass" || value=="password") { telnetPass="pocsag"; } else { telnetPass=value; } Log.print(0,"TELNET Password: xxxx\r\n"); } 93 | else if (cmdLine.startsWith("connect wlan")) { connectWLAN(); } 94 | else if (cmdLine.startsWith("clear wlan")) { wlanSSID=""; connectWLAN(); } 95 | else if (cmdLine.startsWith("restart rx")) { modem.restartRx(true); Log.print(0,"Rx and PLL restarted\r\n"); } 96 | else if (cmdLine.startsWith("restart cpu")) { if (telnetSession.connected()) { telnetSession.stop(); } ESP.restart(); } 97 | else if (cmdLine.startsWith("get flash")) { getFlash(); } 98 | else if (cmdLine.startsWith("read flash")) { readFlash(); } 99 | else if (cmdLine.startsWith("write flash")) { writeFlash(); } 100 | else if (cmdLine.startsWith("erase flash")) { eraseFlash(); } 101 | else if (cmdLine.startsWith("exit")) { modem.monitorRx=false; Log.debug=0; if (telnetSession.connected()) { telnetSession.stop(); } } 102 | else if (cmdLine.startsWith("help")) { help(); } 103 | Log.print(0,"> "); Log.needCR=true; } 104 | 105 | void cliWorker() { 106 | if (Serial.available()) { 107 | char serialByte=Serial.read(); 108 | if (serialByte==127) { Log.write(0,serialByte); cmdLine.remove(cmdLine.length()-1); } 109 | else if (serialByte==10 || serialByte==13) { Log.needCR=false; Log.print(0,"\r\n"); doParse(); cmdLine=""; } 110 | else { Log.write(0,serialByte); cmdLine+=String(serialByte); } } 111 | 112 | if (telnetSession.available() && isAuth) { 113 | char telnetByte=telnetSession.read(); 114 | if (isIAC==3) { isIAC=0; } 115 | if (telnetByte==255 && (!isIAC)) { isIAC++; } 116 | else if (telnetByte==255 && isIAC) { isIAC=0; } 117 | else if (isIAC) { isIAC++; } 118 | else if (telnetByte==127) { Serial.write(telnetByte); cmdLine.remove(cmdLine.length()-1); } 119 | else if (telnetByte==10) {} 120 | else if (telnetByte==13) { Log.needCR=false; Serial.println(); doParse(); cmdLine=""; } 121 | else { Serial.write(telnetByte); cmdLine+=String(telnetByte); } } } 122 | -------------------------------------------------------------------------------- /Flash.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | Preferences flash; 5 | 6 | void getFlash() { 7 | flash.begin("SX1278FSK",true); 8 | Log.print(0,"Center Frequency: %s MHz\r\n",String(flash.getDouble("centerFreq",439.9875),5).c_str()); 9 | Log.print(0,"Rx Frequency Offset: %s kHz\r\n",String(flash.getDouble("rxOffset",7.446),3).c_str()); 10 | Log.print(0,"Bitrate: %s bps\r\n",String(flash.getDouble("bitrate",1.2)*1000,0).c_str()); 11 | Log.print(0,"Shift Frequency: +/- %s Hz\r\n",String(flash.getDouble("shift",4.5)*1000,0).c_str()); 12 | Log.print(0,"Rx Bandwidth: %s kHz\r\n",String(flash.getDouble("rxBandwidth",5.2),1).c_str()); 13 | Log.print(0,"AFC Bandwidth: %s kHz\r\n",String(flash.getDouble("afcBandwidth",25),1).c_str()); 14 | Log.print(0,"BOS Mode: %i\r\n",flash.getBool("isBOS",false)); 15 | Log.print(0,"DAU Filter: %s\r\n",String(flash.getString("daufilter","")).c_str()); 16 | Log.print(0,"WLAN SSID: %s\r\n",flash.getString("wlanSSID","")); 17 | if (flash.getString("wlanSecret","")!="") { Log.print(0,"WLAN Secret: xxxx\r\n"); } else { Log.print(0,"WLAN Secret:\r\n"); } 18 | Log.print(0,"Gateway URL: %s\r\n",String(flash.getString("gwURL","")).c_str()); 19 | Log.print(0,"Raw URL: %s\r\n",String(flash.getString("rawURL","")).c_str()); 20 | Log.print(0,"TELNET Password: xxxx\r\n"); 21 | flash.end(); } 22 | 23 | void readFlash() { 24 | flash.begin("SX1278FSK",true); 25 | modem.stopSequencer(); 26 | modem.setFrequency(flash.getDouble("centerFreq",439.9875),flash.getDouble("rxOffset",7.446)); 27 | modem.setBitrate(flash.getDouble("bitrate",1.2)); 28 | modem.setShift(flash.getDouble("shift",4.5)); 29 | modem.setRxBandwidth(flash.getDouble("rxBandwidth",5.2)); 30 | modem.setAfcBandwidth(flash.getDouble("afcBandwidth",25)); 31 | modem.startSequencer(); 32 | modem.restartRx(true); 33 | modem.isBOS=flash.getBool("isBOS",false); 34 | modem.daufilter=flash.getString("daufilter",""); 35 | wlanSSID=flash.getString("wlanSSID",""); 36 | wlanSecret=flash.getString("wlanSecret",""); 37 | gwURL=flash.getString("gwURL",""); 38 | rawURL=flash.getString("rawURL",""); 39 | telnetPass=flash.getString("telnetPass","pocsag"); 40 | flash.end(); 41 | Log.print(0,"Flash: read\r\n"); 42 | connectWLAN(); } 43 | 44 | void writeFlash() { 45 | flash.begin("SX1278FSK",false); 46 | flash.putDouble("centerFreq",modem.centerFreq); 47 | flash.putDouble("rxOffset",modem.rxOffset); 48 | flash.putDouble("bitrate",modem.bitrate); 49 | flash.putDouble("shift",modem.shift); 50 | flash.putDouble("rxBandwidth",modem.rxBandwidth); 51 | flash.putDouble("afcBandwidth",modem.afcBandwidth); 52 | flash.putBool("isBOS",modem.isBOS); 53 | flash.putString("daufilter",modem.daufilter); 54 | flash.putString("wlanSSID",wlanSSID); 55 | flash.putString("wlanSecret",wlanSecret); 56 | flash.putString("gwURL",gwURL); 57 | flash.putString("rawURL",rawURL); 58 | flash.putString("telnetPass",telnetPass); 59 | flash.end(); 60 | Log.print(0,"Flash: written\r\n"); } 61 | 62 | void eraseFlash() { 63 | nvs_flash_erase(); nvs_flash_init(); 64 | Log.print(0,"Flash: erased\r\n"); } 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Log.h: -------------------------------------------------------------------------------- 1 | #ifndef SX1278_LOG_H 2 | #define SX1278_LOG_H 3 | 4 | class Logging { 5 | public: 6 | uint8_t debug=0; 7 | bool needCR=false; 8 | void (*writeTelnet)(const char)=nullptr; 9 | void (*printTelnet)(const char*)=nullptr; 10 | 11 | Logging() { 12 | Serial.begin(115200); } 13 | 14 | void write(uint8_t level,const char value) { 15 | if (level>debug) { return; } 16 | Serial.write(value); 17 | if (writeTelnet!=nullptr) { writeTelnet(value); } } 18 | 19 | void print(uint8_t level,const char* format,...) { 20 | if (level>debug) { return; } 21 | char buffer[64]; 22 | char* tbuffer=buffer; 23 | va_list args; 24 | va_list copy; 25 | va_start(args,format); 26 | va_copy(copy,args); 27 | int len=vsnprintf(tbuffer,sizeof(buffer),format,copy); 28 | va_end(copy); 29 | if (len<0) { va_end(args); return; } 30 | if (len>=(int)sizeof(buffer)) { 31 | tbuffer=(char*)malloc(len+1); 32 | if (tbuffer==NULL) { va_end(args); return; } 33 | len=vsnprintf(tbuffer,len+1,format,args); } 34 | va_end(args); 35 | 36 | if (needCR) { needCR=false; Serial.println(); 37 | if (printTelnet!=nullptr) { printTelnet("\r\n"); } } 38 | Serial.write((uint8_t*)tbuffer,len); 39 | if (printTelnet!=nullptr) { printTelnet(tbuffer); } 40 | 41 | if (tbuffer!=buffer) { free(tbuffer); } } }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pocsagRX 2 | SX1278 and ESP32 based POCSAG receiver 3 | #### Features 4 | * uses Semtech SX1278 as FSK receiver 5 | * also works with Semtech SX1276 6 | * receives 1200 bps, 4500 Hz shift POCSAG messages 7 | * serial console logging of received messages 8 | * works with direct connected ESP32 (see pin description below) 9 | * also works with HELTEC SX1278 based LoRa module 10 | * activate the HeltecLoRaV2 define statement in SX1278POCSAG.ino to use this module 11 | * serial console Command Line Interface for configuration and debugging 12 | * telnet server Command Line Interface for configuration and debugging 13 | * 3 levels of debugging 14 | * write/read configuration to/from flash 15 | * german BOS mode and DAU filter 16 | * german ASCII codes 17 | * decoding of ROT1 encoded messages 18 | * wlan based https gateway to forwarding the messages 19 | * error correction by BCH decoding 20 | #### SPI bus 21 | * GPIO 19 - MISO 22 | * GPIO 23 - MOSI 23 | * GPIO 18 - SCLK 24 | * GPIO 5 - CS 25 | #### other SX1278 connections 26 | * GPIO 16 - RESET 27 | * GPIO 25 - DIO0 28 | * GPIO 26 - DIO1 29 | * GPIO 27 - DIO2 30 | * GPIO 32 - DIO3 (for future development) 31 | #### Development Hardware 32 | ![IMAGE ALT TEXT HERE](documentation/pocsagRX.png) 33 | #### Console Screenshot (DAPNET DB0HBO) 34 | ![IMAGE ALT TEXT HERE](documentation/console.png) 35 | #### Receiver Configuration 36 | * center frequency in MHz and Rx error offset frequency in kHz 37 | * the Rx error offset is the measured AFC value when Rx error offset is set to 0 38 | * bitrate in kbps (e.g. 1.2 for 1200 bps) 39 | * frequency shift in kHz 40 | * this means the single sided shift (e.g. 4.5 for +/- 4500 Hz) 41 | * Rx bandwidth in kHz (choose the next larger value from table in SX1278.h) 42 | * this means the single sided bandwidth 43 | * calculation: RXbandwidth >= Fshift+(bitrate/2) 44 | * the auto function uses the above formula 45 | * AFC bandwidth in kHz (choose the next larger value from table in SX1278.h) 46 | * calculation: AFCbandwidth >= 2*(Fshift+(bitrate/2))+Ferror 47 | * Ferror means the maximum center frequency offset error of the SX1278 module 48 | * the auto function uses the above formula 49 | #### define statement for the SX1278 based HELTEC LoRa module 50 | activate the HeltecLoRaV2 define statement in SX1278POCSAG.ino to use this GPIO pins 51 | * GPIO 19 - MISO 52 | * GPIO 27 - MOSI 53 | * GPIO 5 - SCLK 54 | * GPIO 18 - CS 55 | * GPIO 14 - RESET 56 | * GPIO 26 - DIO0 57 | * GPIO 35 - DIO1 58 | * GPIO 34 - DIO2 59 | -------------------------------------------------------------------------------- /SX1278.h: -------------------------------------------------------------------------------- 1 | #ifndef SX1278_FSK_H 2 | #define SX1278_FSK_H 3 | 4 | #include "Log.h" 5 | Logging Log; 6 | #include "BCH3121.h" 7 | CBCH3121 bch; 8 | #include 9 | #include "SX1278ISR.h" 10 | #include "WLAN.h" 11 | #include "TELNET.h" 12 | 13 | #ifndef HeltecLoRaV2 14 | #define SCK 18 15 | #define MISO 19 16 | #define MOSI 23 17 | #define CS 5 18 | #define RST 16 19 | #else 20 | #define SCK 5 21 | #define MISO 19 22 | #define MOSI 27 23 | #define CS 18 24 | #define RST 14 25 | #endif 26 | 27 | #define regOpMode 0x1 28 | #define regBrMSB 0x2 29 | #define regBrLSB 0x3 30 | #define regShiftMSB 0x4 31 | #define regShiftLSB 0x5 32 | #define regFreqMSB 0x6 33 | #define regFreqMID 0x7 34 | #define regFreqLSB 0x8 35 | #define regRxLna 0xc 36 | #define regRxCfg 0xd 37 | #define regRssiTresh 0x10 38 | #define regRssi 0x11 39 | #define regRxBw 0x12 40 | #define regAfcBw 0x13 41 | #define regOokPeak 0x14 42 | #define regAfcFei 0x1a 43 | #define regAfcMSB 0x1b 44 | #define regAfcLSB 0x1c 45 | #define regFeiMSB 0x1d 46 | #define regFeiLSB 0x1e 47 | #define regPreambleDetect 0x1f 48 | #define regRxTimeout2 0x21 49 | #define regRxTimeout3 0x22 50 | #define regSynCfg 0x27 51 | #define regSynByte1 0x28 52 | #define regSynByte2 0x29 53 | #define regSynByte3 0x2a 54 | #define regSynByte4 0x2b 55 | #define regPckCfg1 0x30 56 | #define regPckCfg2 0x31 57 | #define regPckLength 0x32 58 | #define regSeqConfig1 0x36 59 | #define regSeqConfig2 0x37 60 | #define regIrqFlags1 0x3e 61 | #define regDioMap1 0x40 62 | #define regDioMap2 0x41 63 | #define regChipVersion 0x42 64 | 65 | struct globalErrors { uint32_t corrected; uint32_t uncorrected; }; 66 | 67 | const double gainValues[8]={0,0,-6,-12,-24,-36,-48,0}; 68 | 69 | const double bwValues[21]= { 2.6, 3.1, 3.9, 5.2, 6.3, 7.8, 10.4, 12.5, 15.6, 20.8, 25, 31.3, 41.7, 50, 62.5, 83.3, 100, 125, 166.7, 200, 250 }; 70 | const uint8_t bwIntegers[21]={ 23, 15, 7, 22, 14, 6, 21, 13, 5, 20, 12, 4, 19,11, 3, 18, 10, 2, 17, 9, 1 }; 71 | 72 | const uint8_t syncWord[4]={0x7c,0xd2,0x15,0xd8}; 73 | 74 | const char bcdCodes[16]={0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x2a,0x55,0x20,0x2d,0x29,0x28}; 75 | 76 | class SX1278FSK { 77 | public: 78 | bool monitorRx; 79 | bool isBOS; 80 | String daufilter=""; 81 | double centerFreq; 82 | double rxOffset; 83 | double bitrate; 84 | double shift; 85 | double rxBandwidth; 86 | double afcBandwidth; 87 | globalErrors errorCount; 88 | uint32_t messageCount; 89 | uint32_t upTime; 90 | String esp32ID; 91 | 92 | SX1278FSK(bool _monitorRx=false, uint8_t _debug=0) { 93 | monitorRx=_monitorRx; Log.debug=_debug; 94 | SPI.begin(SCK, MISO, MOSI, CS); } 95 | 96 | uint8_t readSPI(uint8_t addr) { 97 | digitalWrite(CS, LOW); 98 | SPI.transfer(addr); 99 | uint8_t value = SPI.transfer(0x00); 100 | digitalWrite(CS, HIGH); 101 | return value; } 102 | 103 | void writeSPI(uint8_t addr, uint8_t value) { 104 | digitalWrite(CS, LOW); 105 | SPI.transfer(addr | 0x80); 106 | SPI.transfer(value); 107 | digitalWrite(CS, HIGH); } 108 | 109 | void setReg(uint8_t addr, uint8_t msb, uint8_t lsb, uint8_t value) { 110 | uint8_t oldValue=readSPI(addr); 111 | uint16_t mask=(1<<(msb-lsb+1))-1; 112 | value&=mask; mask<<=lsb; value<<=lsb; 113 | uint8_t newValue=(oldValue&(~mask))|value; 114 | writeSPI(addr,newValue); } 115 | 116 | uint8_t getReg(uint8_t addr, uint8_t msb, uint8_t lsb) { 117 | uint8_t value=readSPI(addr); 118 | uint16_t mask=(1<<(msb-lsb+1))-1; 119 | return (value&(mask<>lsb; } 120 | 121 | void initSPI() { 122 | pinMode(CS, OUTPUT); digitalWrite(CS, HIGH); 123 | SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); } 124 | 125 | void resetChip() { 126 | digitalWrite(RST, LOW); delay(1); 127 | digitalWrite(RST, HIGH); delay(25); } 128 | 129 | void initChip() { 130 | pinMode(RST, OUTPUT); digitalWrite(RST, HIGH); 131 | delay(25); initSPI(); resetChip(); printChip(); 132 | esp32ID=String(ESP.getEfuseMac(),HEX); } 133 | 134 | void regDump() { 135 | for (uint8_t reg=0x00;reg<=0x42;reg++) { uint8_t value=readSPI(reg); 136 | Log.print(0,"0x%02x: 0x%02x %3u 0b%s\r\n",reg,value,value,String(value,BIN).c_str()); } } 137 | 138 | void setFrequency(double _centerFreq, double _rxOffset=0) { 139 | centerFreq=_centerFreq; rxOffset=_rxOffset; 140 | Log.print(0,"Center Frequency: %s MHz\r\n",String(centerFreq+(rxOffset/1000),5).c_str()); 141 | uint32_t value=(uint32_t)round((centerFreq+(rxOffset/1000))*(1<<14)); 142 | writeSPI(regFreqMSB,(value & 0xFF0000) >> 16); 143 | writeSPI(regFreqMID,(value & 0x00FF00) >> 8); 144 | writeSPI(regFreqLSB,value & 0x0000FF); } 145 | 146 | void setBitrate(double _bitrate) { 147 | bitrate=_bitrate; 148 | Log.print(0,"Bitrate: %s bps\r\n",String(bitrate*1000,0).c_str()); 149 | uint16_t value=(uint16_t)round(32000.0/bitrate); 150 | writeSPI(regBrMSB,(value & 0xFF00) >> 8); 151 | writeSPI(regBrLSB,value & 0x00FF); } 152 | 153 | void setShift(double _shift) { 154 | shift=_shift; 155 | Log.print(0,"Shift: +/- %s Hz\r\n",String(shift*1000,0).c_str()); 156 | uint16_t value=(uint16_t)round(shift*(1<<11)/125.0); 157 | writeSPI(regShiftMSB,(value & 0xFF00) >> 8); 158 | writeSPI(regShiftLSB,value & 0x00FF); } 159 | 160 | void setRxBandwidth(double _rxBandwidth) { 161 | uint8_t selected=20; for (uint8_t idx=0;idx<=20;idx++) { 162 | if (bwValues[idx]>=_rxBandwidth) { selected=idx; break; } } 163 | Log.print(0,"Rx Bandwidth: %s kHz\r\n",String(bwValues[selected],1).c_str()); 164 | setReg(regRxBw,4,0,bwIntegers[selected]); rxBandwidth=bwValues[selected]; } 165 | 166 | void setRxBwAuto() { setRxBandwidth(shift+(bitrate/2)); } 167 | 168 | void setAfcBandwidth(double _afcBandwidth) { 169 | uint8_t selected=20; for (uint8_t idx=0;idx<=20;idx++) { 170 | if (bwValues[idx]>=_afcBandwidth) { selected=idx; break; } } 171 | Log.print(0,"AFC Bandwidth: %s kHz\r\n",String(bwValues[selected],1).c_str()); 172 | setReg(regAfcBw,4,0,bwIntegers[selected]); afcBandwidth=bwValues[selected]; } 173 | 174 | void setAfcBwAuto(double error=12) { setAfcBandwidth(2*(shift+(bitrate/2))+error); } 175 | 176 | void setModeFskRxCont() { 177 | setReg(regOpMode,7,7,0); // Mode 0:FSK/OOK 1:LoRa 178 | setReg(regOpMode,6,5,0); // Modulation 0:FSK 1:OOK 179 | setReg(regOpMode,3,3,1); // Frequency Mode 0:High 1:Low 180 | setReg(regOpMode,2,0,1); // Transceiver Mode 0:Sleep 1:Standby 2:FSTx 3:Tx 4:FSRx 5:Rx 181 | setReg(regOokPeak,5,5,1); // Bit Synchronizer 0:Off 1:On 182 | setReg(regRxCfg,7,7,0); // Restart Rx on Collision 0:Off 1:On 183 | setReg(regRxCfg,4,4,1); // AFC Auto 0:Off 1:On 184 | setReg(regRxCfg,3,3,1); // AGC Auto 0:Off 1:On 185 | setReg(regRxCfg,2,0,6); // Rx Interrupt triggers 6:Preamble AGC+AFC 7:Preamble+RSSI AGC+AFC 186 | setReg(regAfcFei,0,0,0); // AFC Auto Clear 0:Off 1:On 187 | setReg(regPreambleDetect,7,7,1); // Preamble Detector 0:Off 1:On 188 | setReg(regPreambleDetect,6,5,1); // Preamble Detector Size 0:1 Byte 1:2 Bytes 2:3 Bytes 189 | setReg(regPreambleDetect,4,0,10); // Preamble Detector Errors tolerated 190 | setReg(regSynCfg,5,5,0); // Preamble Polarity 0:AA 1:55 191 | setReg(regSynCfg,4,4,1); // Sync Word Rx/Tx Processing 0:Off 1:On 192 | setReg(regSynCfg,2,0,3); // Sync Byte Count x:x+1 193 | writeSPI(regSynByte1,syncWord[0]); // Frame Sync Byte 1 194 | writeSPI(regSynByte2,syncWord[1]); // Frame Sync Byte 2 195 | writeSPI(regSynByte3,syncWord[2]); // Frame Sync Byte 3 196 | writeSPI(regSynByte4,syncWord[3]); // Frame Sync Byte 4 197 | setReg(regPckCfg1,7,7,1); // Packet Length 0:Fixed 1:Variable 198 | setReg(regPckCfg1,6,5,0); // DC Free 0:Off 1:Manchester 2:Whitening 199 | setReg(regPckCfg1,4,4,0); // CRC Rx/Tx Processing 0:Off 1:On 200 | setReg(regPckCfg1,2,1,0); // Address Filtering 0:Off 201 | setReg(regPckCfg2,6,6,0); // Data Mode 0:Continuous 1:Packet 202 | setReg(regPckCfg2,2,0,7); // Packet Length MSB 203 | setReg(regPckLength,7,0,255); } // Packet Length LSB 204 | 205 | void startSequencer() { 206 | setReg(regOpMode,2,0,1); // Transceiver Mode 0:Sleep 1:Standby 2:FSTx 3:Tx 4:FSRx 5:Rx 207 | setReg(regRxTimeout2,7,0,4); // Preamble Timeout 0:Off x:x*16*Tbit 208 | setReg(regRxTimeout3,7,0,0); // Frame Sync Timeout 0:Off x:x*16*Tbit 209 | setReg(regSeqConfig1,4,3,1); // Sequence from Start to 0:Low Power 1:Rx 2:Tx 210 | setReg(regSeqConfig2,4,3,0); // Sequence from Rx Timeout to 0:Rx Restart 1:Tx 2:Low Power 3:Off 211 | setReg(regSeqConfig1,7,7,1); } // Sequencer Start 212 | 213 | void stopSequencer() { 214 | timerRx=millis()+2000; 215 | setReg(regSeqConfig1,6,6,1); // Sequencer Stop 216 | setReg(regRxTimeout2,7,0,0); // Preamble Timeout 0:Off x:x*16*Tbit 217 | setReg(regRxTimeout3,7,0,0); // Frame Sync Timeout 0:Off x:x*16*Tbit 218 | delay(100); 219 | setReg(regOpMode,2,0,4); } // Transceiver Mode 0:Sleep 1:Standby 2:FSTx 3:Tx 4:FSRx 5:Rx 220 | 221 | void initDioIf() { 222 | setReg(regDioMap1,7,6,1); // DIO0 Mapping 0:Sync Word 1:RSSI/Preamble Detect 223 | setReg(regDioMap1,5,4,0); // DIO1 Mapping 0:Clock 224 | setReg(regDioMap1,3,2,0); // DIO2 Mapping 0:Data 225 | setReg(regDioMap1,1,0,0); // DIO3 Mapping 0:Timeout 1:RSSI/Preamble Detect 226 | setReg(regDioMap2,0,0,1); // Map Detect Interrupt 0:RSSI 1:Preamble 227 | queueDIO1=xQueueCreate(queueSizeDIO1,sizeof(uint8_t)); 228 | pinMode(DIO0, INPUT); pinMode(DIO1, INPUT); pinMode(DIO2, INPUT); pinMode(DIO3, INPUT); 229 | attachInterrupt(DIO0,dio0ISR,RISING); 230 | attachInterrupt(DIO1,dio1ISR,RISING); 231 | attachInterrupt(DIO3,dio3ISR,RISING); } 232 | 233 | bool available() { 234 | uint8_t lastByte; 235 | return xQueuePeekFromISR(queueDIO1,&lastByte); } 236 | 237 | uint8_t read() { 238 | uint8_t lastByte; 239 | xQueueReceiveFromISR(queueDIO1,&lastByte,NULL); 240 | return lastByte; } 241 | 242 | void restartRx(bool withPLL) { 243 | if (!withPLL) { setReg(regRxCfg,6,6,1); } 244 | else { setReg(regRxCfg,5,5,1); } } 245 | 246 | double getAFC() { 247 | uint8_t valueMSB=readSPI(regAfcMSB); 248 | uint8_t valueLSB=readSPI(regAfcLSB); 249 | int16_t value=(valueMSB<<8)|valueLSB; 250 | return (double)value/16.384; } 251 | 252 | double getFEI() { 253 | uint8_t valueMSB=readSPI(regFeiMSB); 254 | uint8_t valueLSB=readSPI(regFeiLSB); 255 | int16_t value=(valueMSB<<8)|valueLSB; 256 | return (double)value/16.384; } 257 | 258 | double getGain() { return gainValues[getReg(regRxLna,7,5)]; } 259 | 260 | void setRssiTresh(int tresh) { 261 | writeSPI(regRssiTresh,(uint8_t)(tresh*-2)); } 262 | 263 | double getRSSI() { return readSPI(regRssi)/-2.0; } 264 | 265 | uint8_t searchSync(uint8_t rxByte) { 266 | static uint32_t syncBuffer; 267 | for (uint8_t bitPos=0;bitPos<=7;bitPos++) { 268 | syncBuffer<<=1; syncBuffer|=(rxByte&128)>>7; rxByte<<=1; 269 | if (syncBuffer==0x7cd215d8) { return 7-bitPos; } } 270 | return 255; } 271 | 272 | void printChip() { 273 | Log.print(0,"SX1278 Chip Version: %i Hardware Revision: %i\r\n",getReg(regChipVersion,7,4),getReg(regChipVersion,3,0)); } 274 | 275 | void printRx() { 276 | Log.print(0,"RSSI: %s dBm Gain: %s dBm AFC: %s kHz FEI: %s kHz\r\n",String(getRSSI(),1).c_str(),String(getGain(),1).c_str(),String(getAFC(),3).c_str(),String(getFEI(),3).c_str()); } 277 | 278 | void beginPOCSAG() { 279 | setModeFskRxCont(); 280 | initDioIf(); 281 | restartRx(true); 282 | startSequencer(); 283 | delay(500); 284 | timerRx=millis()+1000; 285 | Log.print(0,"POCSAG Rx started\r\n"); } 286 | 287 | String consoleDE(uint8_t code) { 288 | if (isROT1) { code=(code==0)?127:code-1; } 289 | if ((code>0x0 && code<0x20) || code>0x7e) { return "[" + String(code,DEC) + "]"; } 290 | switch(code) { 291 | case 0x0: return ""; break; 292 | case 0x5b: return "\u00c4"; break; 293 | case 0x5c: return "\u00d6"; break; 294 | case 0x5d: return "\u00dc"; break; 295 | case 0x7b: return "\u00e4"; break; 296 | case 0x7c: return "\u00f6"; break; 297 | case 0x7d: return "\u00fc"; break; 298 | case 0x7e: return "\u00df"; break; 299 | default: return String((char)code); } } 300 | 301 | void messageReceived() { 302 | Log.print(1," BCH Errors: %i/%i\r\n",error.corrected,error.uncorrected); 303 | if (gwURL!="") { 304 | if (message=="") { message="no message"; } 305 | String postValue="dme=" + esp32ID; 306 | postValue+="&rssi=" + urlencode(String(rssi,1)); 307 | postValue+="&error=" + String(error.uncorrected); 308 | postValue+="&ric=" + String(ric); 309 | postValue+="&function=" + String(function); 310 | postValue+="&dau=" + urlencode(dau); 311 | postValue+="&message=" + urlencode(message); 312 | postHTTPS(gwURL,postValue); } 313 | error.corrected=0; error.uncorrected=0; message=""; messageCount++; } 314 | 315 | void messageFiltered() { 316 | Log.needCR=false; Log.print(1," filtered out\r\n"); 317 | error.corrected=0; error.uncorrected=0; message=""; } 318 | 319 | void pocsagWorker() { 320 | if (millis()>=timerRx) { timerRx=millis()+1000; 321 | if (isMessageRun) { isMessageRun=false; messageReceived(); 322 | if (isBOS) { isDAU=true; } else { isDAU=false; } 323 | ric=0; function=0x58; dau=""; } 324 | restartRx(false); upTime++; 325 | if (monitorRx) { printRx(); } } 326 | 327 | portENTER_CRITICAL(&mutexDIO0); 328 | if (detectDIO0Flag) { detectDIO0Flag=false; portEXIT_CRITICAL(&mutexDIO0); 329 | Log.print(2,"Preamble Detected!\r\n"); 330 | if (rxOffset==0) { rxOffset=getAFC(); Log.print(0,"Auto Rx Offset: %s kHz detected\r\n",String(rxOffset,3).c_str()); } 331 | if (Log.debug) { printRx(); } 332 | Log.print(2,"Bytes queued: %i/%i\r\n",uxQueueMessagesWaitingFromISR(queueDIO1),queueSizeDIO1); 333 | if (isBOS) { isDAU=true; } else { isDAU=false; } 334 | rssi=getRSSI()-getGain(); 335 | error.corrected=0; error.uncorrected=0; ric=0; function=0x58; dau=""; message=""; 336 | timerRx=millis()+1000; } 337 | else { portEXIT_CRITICAL(&mutexDIO0); } 338 | 339 | if (available()) { 340 | uint8_t rxByte=read(); 341 | uint8_t bitShift=searchSync(rxByte); 342 | 343 | if (bitShift!=255) { 344 | timerRx=millis()+1000; 345 | Log.print(2,"Frame Sync Detected! Bit Shift: %i\r\n",bitShift); 346 | uint32_t batch[16]={0}; 347 | 348 | for (uint8_t idx=0;idx<=63;idx++) { 349 | uint8_t codeWord=idx>>2; 350 | batch[codeWord]<<=bitShift; batch[codeWord]|=rxByte&((1<>bitShift; 353 | if (idx==63 && bitShift!=0) { searchSync(rxByte); } } 354 | 355 | if (rawURL!="") { 356 | String postValue="dme=" + esp32ID; 357 | String rawBatch; 358 | for (uint8_t idx=0;idx<=15;idx++) { rawBatch+=(char)(batch[idx]>>0); rawBatch+=(char)(batch[idx]>>8); rawBatch+=(char)(batch[idx]>>16); rawBatch+=(char)(batch[idx]>>24); } 359 | postValue+="&raw=" + urlencode(rawBatch); 360 | postHTTPS(rawURL,postValue); } 361 | 362 | for (uint8_t idx=0;idx<=15;idx++) { 363 | errors currentError=bch.decode(batch[idx]); 364 | 365 | if (batch[idx]==0x7a89c197) { isIdle=true; } else { isIdle=false; } 366 | 367 | if (!(batch[idx]&(1<<31))) { isAddress=true; } else { isAddress=false; } 368 | 369 | if (isBOS && dau!="") { if (daufilter!="" && (!dau.startsWith(daufilter))) { isMessageRun=false; messageFiltered(); break; } } 370 | 371 | if (isAddress && isMessageRun && (!isIdle)) { isMessageRun=false; messageReceived(); timerRx=millis()+1000; } 372 | 373 | error.corrected+=currentError.corrected; error.uncorrected+=currentError.uncorrected; 374 | errorCount.corrected+=currentError.corrected; errorCount.uncorrected+=currentError.uncorrected; 375 | 376 | Log.print(3,"%02u: ",idx); for (int8_t bit=31;bit>=0;bit--) { 377 | if (bit==30 || (isAddress && bit==12) || bit==10 || bit==0) { Log.write(3,0x20); } 378 | if (batch[idx]&(1<>10)|(idx>>1); 386 | if (ric==4512 || ric==4520) { isROT1=true; } else { isROT1=false; } 387 | Log.print(1," RIC: %i\r\n",ric); 388 | if (isROT1) { Log.print(1," Encoded: ROT1\r\n"); } 389 | function=(batch[idx]&0x1800)>>11; 390 | if (isBOS) { function+=0x41; } else { function+=0x30; } 391 | switch(function) { 392 | case '0': Log.print(1," Message Type: Numeric\r\n"); isText=false; break; 393 | case '1': Log.print(1," Message Type: 1\r\n"); isText=false; break; 394 | case '2': Log.print(1," Message Type: 2\r\n"); isText=false; break; 395 | case '3': Log.print(1," Message Type: Text\r\n"); isText=true; break; 396 | case 'A': Log.print(1," Sub RIC: A\r\n"); isText=true; break; 397 | case 'B': Log.print(1," Sub RIC: B\r\n"); isText=true; break; 398 | case 'C': Log.print(1," Sub RIC: C\r\n"); isText=true; break; 399 | case 'D': Log.print(1," Sub RIC: D\r\n"); isText=true; } } 400 | 401 | if (isAddress) { text=0; textPos=0; number=0; numberPos=0; } 402 | 403 | if (!isIdle) { isMessageRun=true; } 404 | 405 | if ((!isAddress) && isDAU) { isText=false; if (idx==0) { Log.print(1," DAU Address: "); Log.needCR=true; } } 406 | 407 | if ((!isAddress) && isText) { 408 | if (!Log.needCR) { Log.print(1," "); Log.needCR=true; } 409 | for (uint8_t bitPos=30;bitPos>=11;bitPos--) { 410 | text>>=1; text|=(batch[idx]&(1<>(bitPos-7); 411 | textPos++; if (textPos>=7) { text>>=1; 412 | message+=String(consoleDE(text)); 413 | Log.needCR=false; Log.print(1,"%s",consoleDE(text).c_str()); Log.needCR=true; 414 | text=0; textPos=0; } } } 415 | 416 | if ((!isAddress) && (!isText)) { 417 | if (!Log.needCR) { Log.print(1," "); Log.needCR=true; } 418 | for (uint8_t bitPos=30;bitPos>=11;bitPos--) { 419 | number>>=1; number|=(batch[idx]&(1<>(bitPos-7); 420 | numberPos++; if (numberPos>=4) { number>>=4; 421 | if (isDAU) { dau+=String(bcdCodes[number]); } 422 | if (!isDAU) { message+=String(bcdCodes[number]); } 423 | Log.write(1,bcdCodes[number]); 424 | number=0; numberPos=0; } } } } } } } 425 | 426 | private: 427 | uint64_t timerRx; 428 | bool isText; 429 | bool isROT1; 430 | bool isIdle; 431 | bool isAddress; 432 | bool isDAU; 433 | errors error; 434 | uint32_t ric; 435 | char function; 436 | bool isMessageRun; 437 | String dau=""; 438 | String message=""; 439 | bool parity; 440 | double rssi; 441 | uint8_t text=0; 442 | uint8_t textPos=0; 443 | uint8_t number=0; 444 | uint8_t numberPos=0; }; 445 | 446 | #endif 447 | -------------------------------------------------------------------------------- /SX1278ISR.h: -------------------------------------------------------------------------------- 1 | #ifndef SX1278_ISR_H 2 | #define SX1278_ISR_H 3 | 4 | #ifndef HeltecLoRaV2 5 | #define DIO0 25 6 | #define DIO1 26 7 | #define DIO2 27 8 | #define DIO3 32 9 | #else 10 | #define DIO0 26 11 | #define DIO1 35 12 | #define DIO2 34 13 | #define DIO3 32 14 | #endif 15 | 16 | volatile bool detectDIO0Flag=false; 17 | volatile bool detectDIO3Flag=false; 18 | 19 | portMUX_TYPE mutexDIO0=portMUX_INITIALIZER_UNLOCKED; 20 | QueueHandle_t queueDIO1; 21 | UBaseType_t queueSizeDIO1=1024; 22 | portMUX_TYPE mutexDIO3=portMUX_INITIALIZER_UNLOCKED; 23 | 24 | void IRAM_ATTR dio0ISR() { 25 | portENTER_CRITICAL_ISR(&mutexDIO0); 26 | detectDIO0Flag=true; 27 | portEXIT_CRITICAL_ISR(&mutexDIO0); } 28 | 29 | void IRAM_ATTR dio1ISR() { 30 | static uint8_t buffer=0; static uint8_t bufferMask=128; 31 | if (digitalRead(DIO2)==LOW) { buffer|=bufferMask; } 32 | bufferMask>>=1; if (bufferMask==0) { 33 | xQueueSendFromISR(queueDIO1,&buffer,NULL); 34 | buffer=0; bufferMask=128; } } 35 | 36 | void IRAM_ATTR dio3ISR() { 37 | portENTER_CRITICAL_ISR(&mutexDIO3); 38 | detectDIO3Flag=true; 39 | portEXIT_CRITICAL_ISR(&mutexDIO3); } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /TELNET.h: -------------------------------------------------------------------------------- 1 | #ifndef SX1278_TELNET_H 2 | #define SX1278_TELNET_H 3 | 4 | WiFiServer telnetServer(23); 5 | WiFiClient telnetSession; 6 | 7 | uint8_t sessionActive=0; 8 | bool isAuth=false; 9 | String telnetPass="pocsag"; 10 | String passBuffer=""; 11 | int isIAC=0; 12 | 13 | void writeTelnet(const char value) { if (telnetSession.connected() && isAuth) { telnetSession.write(value); } } 14 | 15 | void printTelnet(const char* value) { if (telnetSession.connected() && isAuth) { telnetSession.print(value); } } 16 | 17 | void iacEcho(bool value) { if (value) { telnetSession.print("\xff\xfc\x01"); } else { telnetSession.print("\xff\xfb\x01"); } } 18 | 19 | void initTELNET() { 20 | telnetServer.begin(); 21 | Log.writeTelnet=writeTelnet; 22 | Log.printTelnet=printTelnet; } 23 | 24 | void telnetWorker() { 25 | if (((!telnetSession) || (!isAuth)) && telnetServer.hasClient()) { 26 | telnetSession=telnetServer.available(); 27 | sessionActive=1; isAuth=false; passBuffer=""; 28 | Log.print(0,"TELNET Session from %s connected\r\n",telnetSession.remoteIP().toString().c_str()); 29 | iacEcho(false); telnetSession.print("Password: "); } 30 | 31 | if ((sessionActive || telnetSession) && (!telnetSession.connected())) { 32 | telnetSession.stop(); 33 | sessionActive=0; isAuth=false; passBuffer=""; 34 | Log.print(0,"TELNET Session disconnected\r\n"); } 35 | 36 | if (telnetSession && (!isAuth) && telnetSession.available()) { 37 | char telnetByte=telnetSession.read(); 38 | if (isIAC==3) { isIAC=0; } 39 | if (telnetByte==255 && (!isIAC)) { isIAC++; } 40 | else if (telnetByte==255 && isIAC) { isIAC=0; } 41 | else if (isIAC) { isIAC++; } 42 | else if (telnetByte==127) { telnetSession.write(telnetByte); passBuffer.remove(passBuffer.length()-1); } 43 | else if (telnetByte==10) {} 44 | else if (telnetByte==13) { 45 | if (passBuffer==telnetPass) { 46 | iacEcho(true); isAuth=true; telnetSession.print("\r\n"); Log.print(0,"TELNET Authentication from %s passed\r\n> ",telnetSession.remoteIP().toString().c_str()); passBuffer=""; Log.needCR=true; } 47 | else { if (passBuffer!="") { Log.print(0,"TELNET Authentication from %s failed\r\n",telnetSession.remoteIP().toString().c_str()); } passBuffer=""; telnetSession.print("\r\nPassword: "); } } 48 | else { telnetSession.write("*"); passBuffer+=String(telnetByte); } } } 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /TFT.h: -------------------------------------------------------------------------------- 1 | #ifndef SX1278_TFT_H 2 | #define SX1278_TFT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define TFT_MISO 12 10 | #define TFT_MOSI 13 11 | #define TFT_CLK 14 12 | #define TFT_CS 15 13 | #define TFT_DC 17 14 | #define TFT_RST 4 15 | #define TFT_LED 32 16 | #define Touch_CS 33 17 | #define Touch_IRQ 34 18 | 19 | SPIClass *hspi=new SPIClass(); 20 | Adafruit_ILI9341 tft=Adafruit_ILI9341(hspi,TFT_DC,TFT_CS,TFT_RST); 21 | XPT2046_Touchscreen ts(Touch_CS,Touch_IRQ); 22 | 23 | uint32_t tftTimer=0; 24 | 25 | void tftOn() { digitalWrite(TFT_LED,LOW); tftTimer=millis()+10000; } 26 | void tftOff() { digitalWrite(TFT_LED,LOW); } 27 | 28 | void initTFT() { 29 | pinMode(TFT_LED,OUTPUT); tftOff(); 30 | hspi->begin(TFT_CLK,TFT_MISO,TFT_MOSI); 31 | tft.begin(); 32 | tft.setRotation(3); 33 | tft.fillScreen(ILI9341_WHITE); 34 | tft.setTextColor(ILI9341_BLACK,ILI9341_WHITE); 35 | tft.setTextSize(2); 36 | tft.setCursor(0,0); 37 | ts.begin(*hspi); 38 | ts.setRotation(1); } 39 | 40 | void tftWorker() { if (tftTimer && millis()>=tftTimer) { tftTimer=0; tftOff(); } } 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /WLAN.h: -------------------------------------------------------------------------------- 1 | #ifndef SX1278_WLAN_H 2 | #define SX1278_WLAN_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | WiFiClientSecure client; 9 | HTTPClient https; 10 | 11 | String wlanSSID=""; 12 | String wlanSecret=""; 13 | String gwURL=""; 14 | String rawURL=""; 15 | uint32_t upEvents; 16 | uint32_t downEvents; 17 | int httpStatus; 18 | int httpMaxRetries=2; 19 | uint32_t httpRetried; 20 | uint32_t httpFailed; 21 | uint32_t timerWLAN; 22 | bool hasIP=false; 23 | int waitConnect=0; 24 | 25 | void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info) { 26 | Log.print(0,"WLAN AP: %s with %i dBm connected\r\n",WiFi.SSID(),WiFi.RSSI()); 27 | upEvents++; hasIP=true; } 28 | 29 | void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) { 30 | Log.print(0,"WLAN AP: disconnected\r\n"); 31 | downEvents++; hasIP=false; } 32 | 33 | void initWLAN() { 34 | WiFi.mode(WIFI_STA); 35 | WiFi.onEvent(WiFiStationConnected,ARDUINO_EVENT_WIFI_STA_GOT_IP); 36 | WiFi.onEvent(WiFiStationDisconnected,ARDUINO_EVENT_WIFI_STA_DISCONNECTED); 37 | WiFi.setHostname("pocsagGateway"); 38 | WiFi.setAutoConnect(false); WiFi.setAutoReconnect(false); 39 | client.setInsecure(); https.setReuse(true); timerWLAN=millis()+2000; } 40 | 41 | void connectWLAN() { 42 | WiFi.disconnect(); 43 | if (wlanSSID!="" && wlanSecret!="") { 44 | waitConnect=10; Log.print(0,"WLAN AP: try connect\r\n"); 45 | WiFi.begin(wlanSSID.c_str(),wlanSecret.c_str()); } } 46 | 47 | void postHTTPS(String url, String postData) { 48 | uint8_t attempt=0; while (attempt<=httpMaxRetries) { 49 | if (hasIP) { client.connect(url.c_str(),443); https.begin(client,url); 50 | https.addHeader("Content-Type","application/x-www-form-urlencoded"); 51 | httpStatus=https.POST(postData); 52 | Log.print(2," HTTP POST: %s\r\n",postData.c_str()); 53 | Log.print(1," HTTP Attempt: %i Status: %i\r\n",attempt+1,httpStatus); 54 | https.end(); client.stop(); } else { httpStatus=0; } 55 | attempt++; if (httpStatus==200) { break; } } 56 | if (attempt>httpMaxRetries && httpStatus!=200) { httpFailed++; } 57 | else if (attempt>1) { httpRetried++; } } 58 | 59 | String urlencode(String value) { 60 | String result=""; char x; char x0; char x1; 61 | for (int idx=0;idx9) { x1=(x&0xf)-10+'A'; } 67 | x=(x>>4)&0xf; x0=x+'0'; 68 | if (x>9) { x0=x-10+'A'; } 69 | result+='%'; result+=x0; result+=x1; } } 70 | return result; } 71 | 72 | void wlanWorker() { 73 | if (millis()>=timerWLAN) { timerWLAN=millis()+2000; WiFi.RSSI(); 74 | if (!hasIP) { 75 | if (waitConnect>0) { waitConnect--; } 76 | if (wlanSSID!="" && wlanSecret!="" && waitConnect==0) { connectWLAN(); } } 77 | else { waitConnect=0; } } } 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /documentation/AN142(POCSAG).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErikDorstel/pocsagRX/e912b12d1d7646efec7897191d83446fab9efa9c/documentation/AN142(POCSAG).pdf -------------------------------------------------------------------------------- /documentation/SX127x Datasheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErikDorstel/pocsagRX/e912b12d1d7646efec7897191d83446fab9efa9c/documentation/SX127x Datasheet.pdf -------------------------------------------------------------------------------- /documentation/TR-BOS_Digitale-Funkalarmierung-April-2011_002.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErikDorstel/pocsagRX/e912b12d1d7646efec7897191d83446fab9efa9c/documentation/TR-BOS_Digitale-Funkalarmierung-April-2011_002.pdf -------------------------------------------------------------------------------- /documentation/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErikDorstel/pocsagRX/e912b12d1d7646efec7897191d83446fab9efa9c/documentation/console.png -------------------------------------------------------------------------------- /documentation/pocsagRX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErikDorstel/pocsagRX/e912b12d1d7646efec7897191d83446fab9efa9c/documentation/pocsagRX.png -------------------------------------------------------------------------------- /pocsagRX.ino: -------------------------------------------------------------------------------- 1 | //#define HeltecLoRaV2 2 | 3 | #include "SX1278.h" 4 | SX1278FSK modem(false,0); 5 | #include "CLI.h" 6 | 7 | void setup() { 8 | initWLAN(); 9 | initTELNET(); 10 | modem.initChip(); 11 | modem.beginPOCSAG(); 12 | readFlash(); } 13 | 14 | void loop() { modem.pocsagWorker(); cliWorker(); wlanWorker(); telnetWorker(); } 15 | --------------------------------------------------------------------------------