IRMP-org / IRMP

Infrared Multi Protocol Decoder
GNU General Public License v3.0
267 stars 43 forks source link

Send NEC repeat command #39

Closed dennisfrett closed 3 years ago

dennisfrett commented 3 years ago

Is it possible to send a NEC last command repeat code (0xFFFFFF) directly?

The irsnd_send_data API allows the repeat flags to be set, so it's possible to send a command and then have it repeated. But is it also possible to just send a repeat code by itself, without a preceding command?

ArminJo commented 3 years ago

What is the reason for requiring a repeat directly? Is the repeat parameter (flags) not sufficient?

dennisfrett commented 3 years ago

I'm building an IR repeater box, basically. I want to be able to relay all incoming signals to the receiver in real time. If I would use the flags, I would need to buffer the whole command (wait until the key was depressed, and count the number of repeats), and then relay that command.

What I actually want to do is: system detects 0x00FFAB as a NEC command, send it directly. System detects a repeat command, send it directly.

If I need to buffer all the repeats first, the system cannot react in real time.

Does that make sense?

ArminJo commented 3 years ago

What is the reason to not just use an amplifier for the incoming signal? It has the advantage that it supports all protocols.

dennisfrett commented 3 years ago

The project I have also needs to do "translation" and routing. The issue I have is that I have multiple devices which have an overlap in the NEC codes that they react to. So I just map all the actions I want to 1 single remote, and my Arduino project translates it to the correct one and outputs it to the correct IR LED.

It all works very well, but now when I hold down a volume button, the receiving device just sees the same IR NEC command repeated instead of receiving actual repeat codes. This slows down the speed significant that it changes the volume.

So I can't get by with purely relaying the pulses. It needs to interpret the protocol first.

ArminJo commented 3 years ago

If you only have NEC, then look at https://github.com/Arduino-IRremote/Arduino-IRremote. There is a dedicated void IRsend::sendNECRepeat() function. Disable all other protocols.

dennisfrett commented 3 years ago

I saw that library, but I currently need dynamic output pins (multiple IR LEDs), and this is not supported by that library. I also need to support the Samsung protocol.

dennisfrett commented 3 years ago

@ArminJo I have not looked through the code base in a lot of detail. But is there a (private?) API available that just takes in the protocol and the signal to be sent? In that case it would be easy to add a new public API like sendNECRepeat, that just calls this with NEC and 0xFFFFFF.

ArminJo commented 3 years ago

@ukw100 You are the core developer, do you know if there is a workaround for direct sending a NEC repeat?

dennisfrett commented 3 years ago

Any news on this @ukw100 , @ArminJo ?

ukw100 commented 3 years ago

It is not true that you have to wait until all the repeats have been sent. You are informed of every frame, namely the original frame (flag = 0) as well as every repetition (flag = 1) in realtime, not after the n'th repetition. You could send the NEC frame again for each repetition.

The implementation of a special function sendNECRepeat() breaks the general character of IRMP. At which special request should this end?

To make the request generally available, I could provide a general repeat function for IRSND. If this function gets the NEC protocol as parameter, it creates a repeat frame - in the other cases a normal frame. Then this would be solved in general.

dennisfrett commented 3 years ago

Thanks for you reply!

It is not true that you have to wait until all the repeats have been sent. You are informed of every frame, namely the original frame (flag = 0) as well as every repetition (flag = 1) in realtime, not after the n'th repetition. You could send the NEC frame again for each repetition.

I understand how I can get the the information about how many repeats have been received.

However, I don't see how I can "forward" the repeats?

Could you give an example of this? If I, for example, receive a NEC command, and I see it's the first repetition. At this moment I've already sent out the NEC command (from the first time I've seen it). What do I send out, now that I've detected the repeat? It would be incorrect to send the same NEC command with repeats set to 1, since then the receiving device will have received 2 NEC commands + 1 repeat (total of 3), instead of 2.

What am I missing here?

ukw100 commented 3 years ago

If you receive an IRMP frame with flags = 0x01 (better: if (irmp.flags & IRMP_FLAG_REPETITION)), you could resend this frame with flags = 0x00. So you convert a received repetition-frame into a normal NEC frame. For example a TV accepts a volume command (e.g. 1 NEC plus 3 repetitions) just like it accepts 4 NEC volume frames.

Therefore: receceive NEC frame with flags = 0x00 -> send frame with flags = 0x00 receceive NEC frame with flags = 0x01 -> send frame with flags = 0x00

Then, for example, one NEC frame + 3 repetition frames simply becomes 4 NEC frames on the transmitter side. However, sending a complete NEC frame takes longer than sending an NEC repetition frame, so you may not be able to send all the repetitions received. You would have to try this out.

I have now decided to solve it as follows:

A. I define a new FLAG IRSND_FLAG_ONLY_REPETITION (0x10)

B. If you set this flag in irsnd_data.flags, only the repetition frame is sent if NEC - nothing else.

This means that the API does not have to be changed. IRSND only evaluates the lower 4 bits for repetitions anyway, the upper 4 bits are free for extensions. This change will then appear in the next release.

dennisfrett commented 3 years ago

What you described is what I have now. Repeats are just converted to regular frames. The problem is that some devices interpret 5 NEC frames differently from 1 frame + 4 repeats.

Audio devices, will speed up volume increase / decrease when repeats are received vs regular frames.

I like the idea for the flag change! Looking forward to it, thanks.

ArminJo commented 3 years ago

I am reluctant to merge all changes from the original svn repository into the Arduino sources 😢. It is too much effort for me now. I am not motivated any more to merge the same diffs repeatedly for every upstream change. Sorry.

dennisfrett commented 3 years ago

What original repository do you mean? I will try later if I can just link against both IRMP and IRRemote at the same time without them interfering, then I can just use the repeat command from the latter, but use dynamic pins that IRMP offers.

ArminJo commented 3 years ago

Things are getting better 😀. Today I am waiting for some minor changes upstream, then I will build a new release with the new functionality.

ArminJo commented 3 years ago

Changes are released.

dennisfrett commented 3 years ago

Thanks! I'll give it a try later.

ntzb commented 3 years ago

@dennisfrett did you manage to work it out? I'm in the same situation but the added flag gets no response from my AVR. on long pressing, the volume buttons were detected as:

P=NEC A=0x857A C=0x1A R
P=NEC A=0x857A C=0x1B R

so I tried to define:

avrVolP.protocol = IRMP_NEC_PROTOCOL;
avrVolP.address = 0x857A;
avrVolP.command = 0x1A;
avrVolP.flags = 0x10; <-- used the new flag

and send it in loop while the serial is not empty, but only after first sending with flags = 0. the AVR didn't have any response to the repeat code sent with the 0x10. what am I doing wrong?

dennisfrett commented 3 years ago

I have not yet implemented it in my main project, but I set it up on a breadboard and measured the IR being sent with a logic analyser. The repeats get sent out correctly, but the timing is different from the NEC protocol.

Normally in NEC the main command gets sent out, and if the key is held down, after 40ms the repeat command is sent. Repeat is resent every 108ms.

The problem is that with the current implementation in this library, there is a larger gap between the main NEC command and the first repeat command. The other repeats are sent nicely with 108ms difference.

So if the receiver strictly implements NEC and ignores first repeats that come in way later than 40ms, this will not work.

I suspect that the reading of the IR command does not return fast enough to be able to send the first repeat in time.

@ArminJo Do you know by any chance if it's possible, when holding down the key on the remote, to have the first (non repeat) command completely handled and the repeat sent within 40ms?

ArminJo commented 3 years ago

UPDATED. You must send the first command with irsnd_send_data(&irsnd_data, true); and wait 40ms or send it with irsnd_send_data(&irsnd_data, false); and wait 110ms. Then you set the flag and send the repeat with irsnd_send_data(&irsnd_data, false); and wait 110ms, and so on...

ntzb commented 3 years ago

that's what I was trying, but to no avail. I tried a very basic code:

avrVolP.flags = 0;
irsnd_send_data(&avrVolP, true);
avrVolP.flags = 0x10;
delay(40);
irsnd_send_data(&avrVolP, true);
delay(40);
irsnd_send_data(&avrVolP, true);
delay(40);
irsnd_send_data(&avrVolP, true);

avrVolP is the command I'm sending, with the same address and command as I wrote above, and flags = 0. I only get the first volume change, then nothing.

@dennisfrett would you mind sharing your code as reference?

edit: not sure about the significance of this but using IRRemote, I got this info:

Protocol=NEC Address=0x7A Command=0x1B Raw-Data=0xE41B857A (32 bits) LSB first //first event
Protocol=NEC Address=0x7A Command=0x1B Repeat gap=28000us //first repeat
Protocol=NEC Address=0x7A Command=0x1B Repeat gap=17364us //next repeat
Protocol=NEC Address=0x7A Command=0x1B Repeat gap=17314us
Protocol=NEC Address=0x7A Command=0x1B Repeat gap=17364us

not quite sure what the repeat gap means for practical purposes

dennisfrett commented 3 years ago

@shmizan Here is a sample I've made to reproduce what I'm seeing. This is meant to be an IR "relay" that just forwards what it's seeing. (My main project relies on this being possible to translate certain IR codes):

#include <Arduino.h>

#define IRSND_PROTOCOL_NAMES 1
#define IRMP_PROTOCOL_NAMES 1

#define USE_ONE_TIMER_FOR_IRMP_AND_IRSND
#define IRMP_IRSND_ALLOW_DYNAMIC_PINS
#define IRSND_IR_FREQUENCY 38000

// Only enable needed protocols.
#define IRMP_SUPPORT_NEC_PROTOCOL 1
#define IRSND_SUPPORT_NEC_PROTOCOL 1

#include <irmp.c.h>
#include <irsnd.c.h>

#define IR_RECEIVE_PIN 6
#define IR_SEND_PIN 8

IRMP_DATA irmp_data;

void setup()
{
    irmp_init(IR_RECEIVE_PIN);
    irsnd_init(IR_SEND_PIN);

    Serial.begin(115200);
    Serial.println("Starting");
}

void loop()
{
    if (irmp_get_data(&irmp_data)) {
        const bool isRepeat = irmp_data.flags & IRSND_REPETITION_MASK;

        if (isRepeat) {
            irmp_data.flags = IRSND_RAW_REPETITION_FRAME;
        }

        // irsnd_send_data(&irmp_data, false);
        // irsnd_send_data(&irmp_data, true);
        // irsnd_send_data(&irmp_data, !isRepeat);
        irsnd_send_data(&irmp_data, isRepeat);
    }
}

I've tried with allcombinations of irsnd_send_data(&irmp_data, X); but I'm always getting the following behavior:

result

So all the codes are forwarded, but the timing between the "main command" and the first repeat is wrong at transmit side. The receiver sees a 40ms delay between both (which matches the standard), but my sender has ~90ms delay. All the other repeats are correctly spaced.

@ArminJo Is there something obviously wrong that you see with my code?

dennisfrett commented 3 years ago

I have debugged this further with the following code:

long lastTime = 0;
void loop()
{
    const auto loopStartTime = millis();

    if (irmp_get_data(&irmp_data)) {
        Serial.println("Time since last IR code seen: " + String(loopStartTime - lastTime));
        lastTime = millis();
    }
}

I pressed the button on the remote and held it down, and got the following output:

Time since last IR code seen: 3477
Time since last IR code seen: 51
Time since last IR code seen: 106
Time since last IR code seen: 107
Time since last IR code seen: 106
Time since last IR code seen: 106
Time since last IR code seen: 108
Time since last IR code seen: 107
Time since last IR code seen: 108
Time since last IR code seen: 107
Time since last IR code seen: 108

I checked the time of the loop if I only do the serial printing, and it doesn't seem to be relevant here. It seems to me like it takes at least ~50ms for irmp_get_data to parse a new incoming IR code, which would explain why I am unable to get the send delay down to 40ms.

@ArminJo Would the correct usage here be to buffer the first IR command and see if it is immediately followed by a repeat? In that case I could probably just send with repeats set to 1, and then use the new flag to send all following repeats (~110ms delays). I've been trying this, but even if I send the first repeat together with the main code (just set flags to 1), the timing between the first repeat and the second repeat becomes too large.

I also don't really see a difference between sending with do_wait set to false or true.

ukw100 commented 3 years ago

I have found a solution to the problem. With the next version there is a new flag IRSND_SUPPRESS_TRAILER, which suppresses the longer pause after sending the data. This way you can send a repetition frame immediately afterwards. However, you then have to make the pause between the original frame and the repetition frame yourself.

The flag should only be set when sending the main frame.

Example:

irsnd_send_data(&irmp_data, IRSND_SUPPRESS_TRAILER); delay (40); irsnd_send_data(&irmp_data, IRSND_RAW_REPETITION_FRAME); irsnd_send_data(&irmp_data, IRSND_RAW_REPETITION_FRAME); ...

I will send the necessary changes to Armin today so that he can issue a new version.

dennisfrett commented 3 years ago

@ukw100 Thanks! Once that is in, I can create an Arduino example for a simple IR repeater that does the repeats correctly and create a pull request for it, if you want.

Might be a good starting point for others trying to do something similar.

ArminJo commented 3 years ago

@ukw100 the call with irsnd_send_data(x,false) does exactly the same as the flag IRSND_SUPPRESS_TRAILER! We should decide if we do it via flag or call parameter. If I could start from scratch, I would always skip the trailng space and provide the possibility to enable it with a call parameter.

ukw100 commented 3 years ago

@ArminJo did you overwrite irsnd_send_data()? irsnd_send_data (&data, 0) always sends a trailer, but returns immediateley. If you overwote this function, I cannot accept this, because you make then this incomapatibe with the standard C version.

In the past, IRSND did not add a trailer, which caused various problems. That's why I added it a few years ago.

ArminJo commented 3 years ago

@ukw100 No I did not overwrite it! I only changed the behavior for Arduino not to wait for the trailing space if the last parameter was false, since this was not possible before!

ukw100 commented 3 years ago

@ArminJo the second parameter do_wait controls wether irsnd_send_data() returns immediately or if it waits until the complete frame incl. trailer has been sent. In the first case the application can do somethin other while the ISR is sending the frame (incl. trailer) in the background. Please do not change the previous behaviour. This will lead to incompatibilities and loss of certain features.

I have sent you the necessary changes by e-mail.

ArminJo commented 3 years ago

@dennisfrett You can not use irsnd_send_data(&irmp_data, true); because its implementation is wrong if you send a repeat. This is a global problem with the difference between gaps and period with codes with different code send times. AFAIK NEC is specifies a period of 110 ms, but irsnd implements only one gap length, which is incompatible for codes having different code length. 😞

ukw100 commented 3 years ago

I wrote:

irsnd_send_data(&irmp_data, IRSND_SUPPRESS_TRAILER); delay (40); irsnd_send_data(&irmp_data, IRSND_RAW_REPETITION_FRAME); irsnd_send_data(&irmp_data, IRSND_RAW_REPETITION_FRAME); ...

This is incorrect. I meant:

irmp_data.flags = IRSND_SUPPRESS_TRAILER; irsnd_send_data(&irmp_data, 1); delay (40); irmp_data.flags = IRSND_RAW_REPETITION_FRAME; irsnd_send_data(&irmp_data, 1); irsnd_send_data(&irmp_data, 1); ...

Please excuse the misunderstanding.

ArminJo commented 3 years ago

irsnd_send_data(&irmp_data, 1); irsnd_send_data(&irmp_data, 1);

This gives 40 ms gaps between the repeats, not resulting in 110 ms period 🙁

ukw100 commented 3 years ago

This gives 40 ms gaps between the repeats, not resulting in 110 ms period 🙁

Dennis uses IRMP & IRSND as an IR relais. IRSND sends out what IRMP gets.

Lets's suppose IRMP gets one NEC frame and two NEC repetition frames. These frames already have the correct time distance, so Dennis can immediately send them all without trailers:

irmp_data.flags = IRSND_SUPPRESS_TRAILER; irsnd_send_data(&irmp_data, 1); irmp_data.flags = IRSND_SUPPRESS_TRAILER | IRSND_RAW_REPETITION_FRAME; irsnd_send_data(&irmp_data, 1); irsnd_send_data(&irmp_data, 1);

Here we don't need to insert any delay calls, because we already get the frames with the correct time distance.

ArminJo commented 3 years ago

The solution is to look at the NEC timing https://www.sbprojects.net/knowledge/ir/nec.php and always send with false and do the delay manually to match the timing. I.e. delay the first NEC repeat by 48 ms and all other repeats by 98 ms.

dennisfrett commented 3 years ago

@ArminJo @ukw100 I am confused here. I am trying multiple different things. As @ukw100 said, in principle I should not need to care about the timing, if I just relay the exact thing that comes in. The sending device already ensures the timing is correct.

But even if I'm trying to manually send them:

void loop()
{
  irmp_data.protocol = IRMP_NEC_PROTOCOL;
  irmp_data.flags = IRSND_RAW_REPETITION_FRAME;
  irsnd_send_data(&irmp_data, false);
}

Then everything is spaced 40ms apart, but I would expect this to just not work, since I'm requesting irsnd_send_data to return immediately.

It seems like the sending and / or receiving functions have some delay in them somewhere and don't return immediately.

dennisfrett commented 3 years ago

The reason why we can probably not just do an instant-relay of the incoming signal, is that the first repeat is received while the main command is being sent: result

This means the first repeat is never received by the application, so the first repeat being sent is the second repeat received, and this will break the timing. (In the screen you see 6 repeats received, only 5 sent).

I guess the only way to do this is to buffer the main command for a short while to see if a repeat will come in, and then it can be sent right between the first and the second repeat come in.

dennisfrett commented 3 years ago

It seems that irmp_get_data just returns true too slow now. result

The first line is the received IR signal (from the IR receiver itself). The second line is the sent signal (see code below). The third line is the pulse that goes high whenever irmp_get_data returns true. (D9)

This code buffers the main command until one repeat is received.

IRMP_DATA main_cmd = {IRMP_NEC_PROTOCOL, 0xFF00, 0x40};
bool mainFrameSent = false;

...

void loop()
{
  if (irmp_get_data(&test)) {
      if(test.flags == 0){
        mainFrame = test;
        mainFrameSent = false;
        return;
      }

      digitalWrite(9, HIGH);
      if(mainFrameSent){
        irsnd_send_data(&repeat, false);
      } else {
        irsnd_send_data(&mainFrame, false);
        mainFrameSent = true;
      }
      digitalWrite(9, LOW);
  }
}

As you can see it takes much longer to get back into the irmp_get_data if-body after the main command was sent.

Is this expected?

ArminJo commented 3 years ago

As you can see it takes much longer to get back into the irmp_get_data if-body after the main command was sent.

No this was a bug! I just corrected it. Thanks for reporting 👍 .

dennisfrett commented 3 years ago

@ArminJo I've tested latest master and I'm still seeing the same behavior. (I've made absolutely sure I'm using this version of the library).

Could it be that something else is causing the wait?

dennisfrett commented 3 years ago

@ArminJo is this something that is being looked into? Can I help somehow to debug it? Or is it actually expected?

ArminJo commented 3 years ago

It is currently as designed, but not as required. I am going to change this behavior. The current version 3.x of IRremote now supports variable output pins 😀 .

ArminJo commented 3 years ago

Ok I changed the behavior as promised. No wait after send any more 😀 .

ntzb commented 1 year ago

@ArminJo Hi, I still think there's an issue with IRMP regarding the waiting and NEC repeat. I tried with and without waiting, but the timing has these problems:

  1. not consistent. with one Arduino (IRMP) sending, and another (IRMP) receiving, I see that most command are sent in a similar delay (not identical), but some commands are send after 20ms, or after 180ms...
  2. cannot get delay of less than 45ms between the FIRST command and the next (first repeat) command.

e.g. running this (test) code:

IRMP_DATA avrVolP;

avrVolP.protocol = IRMP_NEC_PROTOCOL;
avrVolP.address = 0x857A;
avrVolP.command = 0x1A;

IRMP_DATA avrRepeat;

avrRepeat.protocol = IRMP_NEC_PROTOCOL;
avrRepeat.address = 0x857A;
avrRepeat.command = 0x0;
avrRepeat.flags = IRSND_RAW_REPETITION_FRAME;

irsnd_send_data(&avrVolP, false);
delay(48);
irsnd_send_data(&avrRepeat, false);
delay(98);
irsnd_send_data(&avrRepeat, false);
delay(98);
irsnd_send_data(&avrRepeat, false);
delay(98);
irsnd_send_data(&avrRepeat, false);
delay(98);

does not work, not matter how much I play with the timing (40 <= t1 <=48 or 95 <= t2 <= 105, or the waiting (true/false).

on the other hand, it works on the first try on IRremote with this (practical) code:

IrSender.sendNEC(avrVolP.address, avrVolP.command, 0);
delay(40);
while (Serial.read() != -1) {
  IrSender.sendNECRepeat();
  delay(98);
}

since I have a working solution on IRremote, I switched to that, but I thought I should update on this, in case anyone is interested.