jgromes / RadioLib

Universal wireless communication library for embedded devices
https://jgromes.github.io/RadioLib/
MIT License
1.52k stars 382 forks source link

POCSAG protocol #7

Closed sp2ong closed 2 years ago

sp2ong commented 5 years ago

It will be nice to have support in RadioLib POCSAG protocol

https://www.raveon.com/pdfiles/AN142(POCSAG).pdf https://habr.com/en/post/438906/ examples sound recorded POCSAG: https://www.sigidwiki.com/wiki/POCSAG

At current exist big project which use PAGERS with 1200 bps POCSAG protocol DAPNET

http://hampager.de

With RadioLib and if you add support POCSAG protocol, we can build own small pagers with Arduino or ESP32 +OLED display and any board supported by RadioLib.

jgromes commented 5 years ago

Looks fairly straight-forward, though it would be nice to have the option to verify with actual device that supports the protocol. Since I don't have any, would you be willing to verify it?

sp2ong commented 5 years ago

Thank you for replay I will be possible to verify receive because I have an available local transmitter of POCSAG DAPNET transmitter.

I have found a nice hardware solution to build a small Receiver PAGER base on: https://www.ebay.com/itm/433Mhz-SX1278-Lora-0-96inch-OLED-Display-ESP32-WIFI-Bluetooth-Development-Board-/172919970108 It looks we need to add power Li-ION 3.7V

I found simple code Arduino decoder but it is only read audio from demodulator on Motorola pager RF boards. https://github.com/tdewaha/arduino-pocsag

Thank you for that you are interested to add POCSAG to RadioLib and I think that many people will benefit from this solution for low-cost PAGER POCSAG I have modules to tests RFM96 and OLED display and I would like to buy ESP32+SX1278+OLED display

I have bought module CC1101 and I can test on CC1101 and RFM96 modules

sp2ong commented 5 years ago

I don't know where you live but in Europe exist many transmitters DAPNET on frequency 439.9875 MHz

My local DAPNET POCSAG transmitter working on 438.175 MHz

You can find at https://hampager.de/#/ an interactive map with the location of the DPANET POCSAG transmitters, you may find it close to your city

sp2ong commented 5 years ago

Every pager has its unique number RIC -Radio Identity Code for example 2025038, 0034145 (is limited to 2^21 = 2097152 ), Typical Pager receive all messages but when finding in receiver messages own RIC number Pager make a short sound (buzzer) and display incoming messages. We can define own unique number and add a few other numbers PAGERS here is group messages. For this reason, we can receive a personal message and group messages.

sp2ong commented 5 years ago

Hi, I would like to ask if any attempts to add the RX POCSAG receiver example for CC1101 have been made for testing?

jgromes commented 5 years ago

Hi, I'm still working on this (that's why this issue is still open), but I'm a bit busy right now. This is only a project I work on in my free time, which was fairly limited lately.

sp2ong commented 5 years ago

Hi,

Thank you for replay and understands your situation. Good luck

Best regards Waldek

sp2ong commented 5 years ago

Hi,

In the meantime, I would like to build simple construction use CC1101 and Arduino Pro Mini 3.3V version. Can you give me information which pins is used in RadioLib for Arduino to connect CC1101 ?

CC1101 -> Arduino Pro Mini GND - > GND VCC - > 3.3V CS -> D10 SI (MISO) -> D12 SO (MOSI) -> D11 SCK -> D13 GDO0 -> D2 GDO2 -> D3

I would like to prepare an image with a connection similar to this: https://www.aeq-web.com/media/93480DA07062018125800.png

First I would test this construction with your example RTTY Beacon

I will define in INO file:

CC1101 fsk = RadioShield.ModuleA;

jgromes commented 5 years ago

RadioShield.ModuleA is a shorthand for CS D10, INT0 (GDO0) D2 and INT1 (GDO2) D3, the rest is SPI bus. You can use CC1101 fsk = new Module(cs, int0, int1); to define custom pins.

sp2ong commented 5 years ago

OK, thank you, I updated description in my last post according to your information but good to know that it is possible to define own PINS

sp2ong commented 5 years ago

Hi,

Can you advise me what version Arduino IDE use with RadioLib ? I use 1.6.7 and 1.6.12 but I have seen that at current is 1.8.9 version

sp2ong commented 5 years ago

I am asking about Arduino IDE version becasue when I have try compile:

Arduino:1.8.9 (Linux), Płytka:"Arduino Pro or Pro Mini, ATmega328P (3.3V, 8 MHz)"

libraries/RadioLib/ISerial.cpp.o (symbol from plugin): In function ISerial::ISerial(Module*)': (.text+0x0): multiple definition ofISerial::ISerial(Module)' sketch/ISerial.cpp.o (symbol from plugin):(.text+0x0): first defined here libraries/RadioLib/ISerial.cpp.o (symbol from plugin): In function `ISerial::ISerial(Module)': (.text+0x0): multiple definition of `ISerial::ISerial(Module*)' ..... collect2: error: ld returned 1 exit status exit status 1 Błąd kompilacji dla płytki Arduino Pro or Pro Mini.

Ten raport powinien zawierać więcej informacji jeśli w File -> Preferencje zostanie włączona opcja "Pokaż szczegółowe informacje podczas kompilacji"

jgromes commented 5 years ago

The IDE version shouldn't matter - I'm using 1.8.8 and Travis builds are at 1.8.1. Could you please check that you don't have the library twice?

sp2ong commented 5 years ago

Ok, it looks that RadioLib I can not use with Arduino IDE under Linux. When I have run under Windows 7 Arduino IDE v1.8.9 example of RTTY_Transmiter compile without problems for Arduino Pro Mini board.

But when I have changed to CC1101 board instead of SX1278

C1101 fsk = RadioShield.ModuleA;

https://github.com/jgromes/RadioLib/blob/master/examples/RTTY/RTTY_Transmit/RTTY_Transmit.ino

the following error was displayed

RTTY_Transmit:35:19: error: 'class CC1101' has no member named 'beginFSK'

int state = fsk.beginFSK();

               ^

exit status 1 'class CC1101' has no member named 'beginFSK'

jgromes commented 5 years ago

I doubt Linux is the issue - all Travis virtual machines are linux as well.

That's because with SX1278, I have to distinguish between LoRa and FSK modem - CC1101 only has FSK, so the only method is begin(). See the CC1101 examples for details.

sp2ong commented 5 years ago

Ok thank you for the suggestion about beginFSK() Yes, you are right, I have twice RadioLib. After correct problem now compile under Linux with Ardiuno v1.8.9 without problem example RTTY_Transmiter for CC1101

jgromes commented 5 years ago

Hi, I got back to this after a few months. It looks like I was a bit too optimistic in the original "fairly straight forward" statement. Everything I added so far in regards to this issue is in the development branch /src/procols. There's a few roadblocks:

  1. I don't have any device capable of receiving the mesasages, so I can't verify my implementation of the protocol.
  2. The documentation for the protocol is sparse and at some points different sources contradict each other.

I'm closing this issue for now, seeing as it's incomplete and I probably won't have time to work on it in the near future.

brooksbUWO commented 2 years ago

There's a few roadblocks:

1. I don't have any device capable of receiving the mesasages, so I can't verify my implementation of the protocol.

2. The documentation for the protocol is sparse and at some points different sources contradict each other.

Any chances of finishing this? I have a receiver capable of receiving the messages and would be able to test it.

jgromes commented 2 years ago

@brooksbUWO Now this is a blast from the past...

Overall, I would rate this as "not worth the effort". The protocol is quite convoluted, and I don't see much use for it.

brooksbUWO commented 2 years ago

I like your RadioLib and I'm trying to use it with a TTGO-LoRa32-V2.1 T3_V1.6.1 (SX1276) to encode a POCSAG message to wirelessly sync time on a BRG Precision LED sign. The POCSAG encoded message is the set time command function formatted following the BRG Precision LED sign protocol v1.2. The sign has a YLI-707 POCSAG Remote Controller & Messaging Receiver module. Inside the YLI-707 is a RMB-500XS450S/900S UHF band data receiver.

I can connect to the serial port on the sign and set the time using the set time function described in their protocol. I have verified the POCSAG message is the same command used when physically connected to an individual sign. Here's the decoded POCSAG message.

I have not YET successfully sent a page to the sign. Maybe the TTGO-LoRa32-V2.1 T3_V1.6.1 with SX1276 is unable to send pages? I tried completing your Pager.cpp from the development branch and for your viewing annoyance it is here. In the example file is my attempt at setting up the SX1276.

What do you specifically think is convoluted in the protocol? Recommendation 584 Standard Codes and Formats for International Radiopaging https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-2-199711-I!!PDF-E.pdf https://www.raveon.com/pdfiles/AN142(POCSAG).pdf

Any advice or is this still rated as "not worth the effort"?

jgromes commented 2 years ago

@brooksbUWO the main issue I had was that I was not able to correctly encode the message using the BCH(31,21) code. I did find some implementation of it (e.g. here https://github.com/phl0/POCSAG_HS/blob/master/BCH3121.cpp), but was never able to verify it's working correctly, because I had no way to test.

Basically, the situation was - I have no way to test, it's not a very widespread protocol, so why bother.

However, in the 3+ years since this was posted, an SDR decoder seems to have appeared: https://github.com/Dustify/SdrSharpPocsagPlugin. So it might be worth bringing this issue back from the dead after all. I like to think I'm more experienced now than I was, so I might have a better chance of cracking it this time around.

Regarding your code, I don't think you're implementing the BCH ECC correctly, you're treating it as a CRC - that's not what it is. BCH is an error correcting code, BCH(31,21) can fix up to two bit errors. CRC is just an error detection code, IIRC it cannot fix bit errors, or even tell you how many there are.

brooksbUWO commented 2 years ago

the main issue I had was that I was not able to correctly encode the message using the BCH(31,21) code. I did find some implementation of it (e.g. here https://github.com/phl0/POCSAG_HS/blob/master/BCH3121.cpp), but was never able to verify it's working correctly, because I had no way to test.

I appreciate your tip on the BCH.

Incase others in the future are interested in this topic, here are two additional resources: POCSAG Encoder from codeproject.com explains the encoding a text message for pagers using POCSAG encoder and source code.

BCH from codeproject.com explains an Implementation of BCH Error Correcting Code (ECC) and source code.

jgromes commented 2 years ago

@brooksbUWO FYI those codeproject entries you linked to are absolute crap - there are errors even in the basic POCSAG message encoding. Also the code is borderline unreadable and explains nothing about how BCH works. That being said, I was able to reuse the BCH encoder, after fixing some issues in it.

Here's a preview of POCSAG sent by RadioLib being deoced by PDW! I still need to add numeric-only messages and tone-only transmissions, and then do a full cleanup, add examples etc.

Screenshot_29

Still not sure it was worth it though ...

brooksbUWO commented 2 years ago

Thank you for looking into this further.

those codeproject entries you linked to are absolute crap

There are errors in the codeproject example with the POCSAG message encoding. Other projects that I looked at didn't follow the standard either.

Still not sure it was worth it though ...

It is worth it.

sp2ong commented 2 years ago

I agree It is worth it.

jgromes commented 2 years ago

Well, this was a journey. And its done now - RadioLib can transmit and receive POCSAG messages. Tone-only, numerical and ASCII.

Screenshot_31

@sp2ong I'm now looking forward to those RadioLib-powered pagers ;) With two-way communication even!

One thing that is still missing for now is decoding the BCH error code on reception and actually using it to correct bit flips. However, that's something for a future me to worry about.

sp2ong commented 2 years ago

@jgromes Thanks, I post announce this news on DAPNET UniPager project

https://github.com/rwth-afu/UniPager/issues/137#issuecomment-1264379189

brooksbUWO commented 2 years ago

I appreciate your effort to add POCSAG protocol to your universal library. When I use it to send a command which contains nulls it does not work. The state after sending the page returns -1001 which according to your documentation is RADIOLIB_ERR_INVALID_PAYLOAD

In your Pager.cpp the following is the cause: if(((data == NULL) && (len > 0)) || ((data != NULL) && (len == 0))) { return(RADIOLIB_ERR_INVALID_PAYLOAD); }

The message I need to send contains NULLs as part of the command as seen in the following screenshot. 2022-10-01 12_02_58-Pager cpp - Sign Wireless - Visual Studio Code

I know it is possible to send NULLs in a POCSAG page because the outdated broken system that I'm attempting to upgrade does work with NULLs. 2022-10-01 12_08_47-

I could change your Pager.cpp code to if(((data != NULL) && (len == 0))) { return(RADIOLIB_ERR_INVALID_PAYLOAD); } and it should work with NULLs in the message.

I am curious why did you write it the way you did?

Thank you again!

jgromes commented 2 years ago

@brooksbUWO the condition data != NULL does not check whether data contains NULL bytes - it checks whether the bufffer itself is NULL.

I would have to see your code, but I susoect you are using the PagerClient::transmit(const char* str, uint32_t addr, uint8_t encoding) method that takes input data as C-string, which is of course null-terminated. To transmit arbitrary binary data, there's the PagerClient::transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding) method.

brooksbUWO commented 2 years ago

I susoect you are using the PagerClient::transmit(const char* str, uint32_t addr, uint8_t encoding) method that takes input data as C-string, which is of course null-terminated.

Your suspicion was partially correct. I actually tried all three of the PagerClient::transmit methods, but didn't include the length on the PagerClient::transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding) method which caused the esp32 to abort().

Here's the results from them:

//int16_t PagerClient::transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding)
state |= pager.transmit(textmsg, cmdTime.datalength, address, RADIOLIB_PAGER_ASCII);
Serial.print(F("Pager transmit code = "));
Serial.println(state);  // Pager transmit code = 0

//int16_t PagerClient::transmit(const char* str, uint32_t addr, uint8_t encoding)
state |= pager.transmit(command, address, RADIOLIB_PAGER_ASCII); // state=-1001
Serial.print(F("Pager transmit code = "));
Serial.println(state);  // Pager transmit code = -1001

//int16_t PagerClient::transmit(String& str, uint32_t addr, uint8_t encoding) 
state |= pager.transmit(message, address, RADIOLIB_PAGER_ASCII); // state=-1001
Serial.print(F("Pager transmit code = "));
Serial.println(state);  // Pager transmit code = -1001

So the PagerClient::transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding) methods returns state=0 except the BRG Precision LED sign's pager reception module does not recognize the command. The exact same command will set the time when I am physically connected to the sign's serial port. However it is not feasible to connect each one of 100's of these signs to keep the time synced.

I'm going to have to do more with PDW and decode the output from your Pager and compare it to the failing system to see why yours does not work. One way to compare would be to use Python OR it is maybe easier capturing the digital output before it is transmitted.

It looks like I can output the encoded message to the Serial Monitor in your code just before it is transmitted in the following section:

void PagerClient::write(uint32_t* data, size_t len) {
   printPOCSAG(data, len);
  // write code words from buffer
  for(size_t i = 0; i < len; i++) {
    PagerClient::write(data[i]);
  }
}

Where printPOCSAG is the following method:

#include <bitset>
void printPOCSAG(const uint8_t* data, uint8_t len)
{
        Serial.println();
        for (int i=0; i<len; i=i+4)
        {
            // Output encoded message in HEX
            Serial.printf("%02X%02X%02X%02X \t", data[i], data[i+1], data[i+2], data[i+3]);

            // Output encoded message in binary
            Serial.printf("%s", std::bitset<sizeof(data[i]) * 8>(data[i]).to_string().c_str());
            Serial.printf("%s", std::bitset<sizeof(data[i+1]) * 8>(data[i+1]).to_string().c_str());
            Serial.printf(" %s", std::bitset<sizeof(data[i+2]) * 8>(data[i+2]).to_string().c_str());
            Serial.printf("%s \r\n", std::bitset<sizeof(data[i+3]) * 8>(data[i+3]).to_string().c_str());                                    
        }
        Serial.println();
}

If I can see the differences in the encoded messages, I'll let you know if you are curious. Again thank you for your quick responses and nice code!

brooksbUWO commented 1 year ago

I would have to see your code

Here's my test code with a real command to send using using all three of PagerClient::transmit methods. The serial monitor outputs the following:

main cpp - POCSAG Test

The PagerClient::transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding) is successful except the original system's pager receiver module does NOT receive it. I think the reason it does not work is because the original system transmits with a Continuous Tone-Coded Squelch System (CTCSS) using a 100 Hz tone.

I have some questions.

1) Why can I not successfully use all of the 3 PagerClient::transmit methods? 2) Can you add to the POCSAG protocol AFSK mode? 3) If you add AFSK, I'm guessing it would follow similar to your other examples?

SX1276 radio = new Module(LORA_CS, LORA_IRQ, LORA_RST, LORA_D1);
AFSKClient audio(&radio, 5);        // Create AFSK client instance using the FSK module
PagerClient pager(&audio);      // Create Pager client instance using the AFSK instance
....
....
// Initialize SX1276
Serial.print(F("[SX1276] Initializing ... "));
int state = radio.beginFSK(462.45, 1.2, 4.5, 12.5, 2, 0, false);

// Initialize Pager client
Serial.print(F("[Pager] Initializing ... "));
state = pager.begin(100, 1200);     // 100 Hz CTCSS, 1200 bps
jgromes commented 1 year ago

@brooksbUWO to answer your questions:

  1. Test#3b fails, because your data contains NULL bytes, so you cannot treat it as a C-string (because its length will be zero). This is the first one that fails, and because you are using |= to bitwise or the results of new tests, you think the others have failed as well, while it is not the case (because -1001 | 0 = -1001).

  2. In theory, you can do that in RadioLib, however, that's not how POCSAG works, it is defined to use FSK, not AFSK. How do you know your system is using that? Also, the ability to receive it would be lost for RadioLib.

  3. Yes, the API would remain the same.

brooksbUWO commented 1 year ago

2. How do you know your system is using that?

The data radio used in the system is a Midland SD225.

It is programmed with CTCSS 100 Hz. Radio Settings - CTCSS

It is using AFSK. Radio Settings - Modem

jgromes commented 1 year ago

@brooksbUWO that is very strange, the POCSAG protocol defines it has to be FSK. I'll see if I can add AFSK support.

brooksbUWO commented 1 year ago

@brooksbUWO that is very strange, the POCSAG protocol defines it has to be FSK. I'll see if I can add AFSK support.

You are correct that it is modulated as Fast-FSK (FFSK) which just another name for minimum-shift keying (MSK). HDSDR_20220729_211143Z_462450kHz_AF Waveform

Maybe this will offer some additional insight. Here's a zipped wav file recording (same one as the above image) from HDSDR. HDSDR_20220729_211143Z_462450kHz_AF.zip

The POCSAG receiver is programmed with the tone information as well. BRG-1720 Programming02

Here's a good article: Minimum shift keying: Subbarayan Pasupathy, Minimum Shift Keying: A Spectrally Efficient Modulation, IEEE Communications Magazine, 1979. Minimum shift keying - A spectrally efficient modulation IEEE.pdf

jgromes commented 1 year ago

@brooksbUWO it's not entirely clear to me what are you trying to say.

I had a look at your request to add AFSK, and realized it doesn't make much sense - POCSAG protocol has the following to say: Screenshot_34

So, if your RF carrier is at 434 MHz, your POCSAG-compatible FSK digital 0 frequency is 434.0045 MHz, and the digital 1 frequency is 433.9955 MHz - that's easy.

For AFSK though, things break down because you would have to set an arbitrary "audio carrier frequency" agains which to shift the tones. Let's start with 5000 Hz. Then, to keep with the required +- 4.5 kHz shift, the digital 1 would be at 500 Hz, and the digital 0 at 9500 Hz. That's a huge bandwidth, NFM demodulators in most SDR software I've seen don't even go that high.

You are correct that it is modulated as Fast-FSK (FFSK) which just another name for minimum-shift keying (MSK).

I'm fairly certain none of the modes described in the standard are MSK. Modulation index of MSK is 0.5. Modulation indexes of the POCSAG modes mentioned in specification are the following:

So I would suggest to examine in detail what exactly is your system using. One of the screenshots you showed lists "V23" (assuming this: https://en.wikipedia.org/wiki/ITU_V.23) and "Bell 202" (https://en.wikipedia.org/wiki/Bell_202_modem). So maybe it's using one of those two modes to send the POCSAG bits?

brooksbUWO commented 1 year ago

I had a look at your request to add AFSK, and realized it doesn't make much sense

My understanding is that the presence of a CTCSS tone on a carrier opens the audio-squelch circuit of the receiver, allowing the signal modulation to pass through. The carrier frequency is 462.45 MHz and it is using AFSK because audio can be sent to the POCSAG receiver module. The screenshot showing "V23" is how the data radio is programmed.

Any suggestions on how I can verify if it is using: V.23 Mode 1 AFSK, V.23 Mode 2 AFSK, or V.23 backward channel?

jgromes commented 1 year ago

@brooksbUWO This has strayed quite far from the original point - I'm going to create a discussion for this (#601)

ManoDaSilva commented 1 year ago

Thanks to your hard word, this can now be used to build a POCSAG "parrot" repeater! I'll make some tests and see if it works. If it does, I'll make a PR with an example sketch ;)

jgromes commented 1 year ago

@ManoDaSilva thanks, though I don't think it's necessary to create a separate example for this. Instead, there's the Show & tell category in the Discussions ;)

ManoDaSilva commented 1 year ago

Got it, I'll put it there then

ManoDaSilva commented 1 year ago

Hi! Sorry again, I started messing around with the SX1278 on an ESP32 TTGO board on "Transmit" mode, and with a GP2009 pager on 439.98750. However, the pager wasn't receiving anything, until I "inverted" the 2-FSK phase in the pager's configuration.

To make it compatible with the other transmitters I'm using, I'd like to be able to invert that polarity within RadioLib... (and it could be a useful feature all-round).

I tried flipping the signs at lines 438 and 441 of pager.cpp, however only the first character of the alphanumeric or numeric message gets decoded when I do that...

So:

Do you have any suggestions of where I could start looking?

EDIT: now it works when I flip those signs... I wonder it it wasn't some weird bug that requires a power cycle... I think that "invert" polarity feature would still be useful ;)

jgromes commented 1 year ago

@ManoDaSilva since I don't own a real pager, I was only testing against PDW, so I'm not sure how are the real devices going to behave.

It could be possible to add inversion. However, are you sure the frequency of your SX1278 is actually correct? Those devices typically have a very bad crystal and can be off by a few kHz. When testing reception, I had to actually tune frequencies a bit to get it to work.

ManoDaSilva commented 1 year ago

POCSAG signal inversion is a known issue, and other solutions also implement it: Unipager (RPi-based pager transmitters), PDW , etc.

I've had that freq stability issue some time ago with another project that used the same ESP32/SX1278 topology (RPS ), but my pager doesn't seem to mind.

I need to test it further with another brand, but inverting the signal really changed from "no decode at all" to "100% messages decoded". I'll let you know how it goes!

jgromes commented 1 year ago

@ManoDaSilva I added option to invert frequencies to `Pager::begin(). I also found out that the previous implementation actually did have swapped frequencies - looks like PDW doesn't care about it.

ManoDaSilva commented 1 year ago

Yup, that did it! Tested on a Swissphone DE915, GP2009N and Motorola Memo (French Tatoo), they all work perfectly with that new fix, and no frequency correction needed :)