DoubleHP post at 2018-2-18 04:52:42

Infrared proxy for aircooling system without LIRC

Edited by DoubleHP at 2018-2-19 06:06

The final aim of the project is to develop a multifunction proxy for an air cooling system.

I want to be able to control the indoor units (I may call them terminals) via network, shut them down, change temperature, or be able to perform any operation remotely.

Because this installation is in an hostel, I also want to restrict the usage of users. Customers of the hostel are allowed to switch the system on and off, and choose temperature, but I want to limit some aspects to avoid various issues.

The master unit is
AOY30LMAW4

The detailed schematics are here:
http://www.airclim.com.ua/userfi ... BAJ_AOY_LMAW4sm.pdf

***

Users are allowed to manipulate the infra red controller.

The first problem I had was that some users have set the units to a too high value, and this may over consume electricity. To avoid this, I need to analyse the IR frames sent from the remote, to the unit, and do some tricks. Then, when I see a user asked for 26°C, I send a new frame with temperature set to 24°C.

The second issue is that the master unit manages 4 indoor terminals; when one is set as heater, and an other one as cooler, then, the master unit goes in "locked mode", and does nothing at all, but just blinks everywhere. To prevent this, I will force all terminals to always work in automatic mode.

The third issue is that I want an anti-freeze mode. The system does not allow any temperature below 18°C. I do not want to heat the whole house at 18°C when it's empty during weeks long; during winter, I only want to heat it at +6°C. And if indoor tempearture is between +6 and +18, I want to keep the whole system shutdown.

I also wanted to try to make the pi invisible; and avoid using a true IR emeter.

*** Hardware part, air cooling system.

The indoor units are very similar to the ones shown on page 9 of the above PDF.

Why do I have great hope things can be done when looking at this schematic ? I see several power supplies, connectors, and wires. The unit is not one big PCB, but there are wires around, and I can easily access them.

Points of interessed are:
- on top right side, in the indicator PCB, the SBX1810 is an infra red receiver. The out pin is the line 201-7, which becomes line 10-8, which goes to pin 63 after a weak pullup resistor. This part is designed the classic way; data line has a pullup, logic is inverted, what means, the line usually sleeps at +5V; and is shortcircuited to 0V to transmit things. I will call this line IR-OUT, or IR-DATA. In practice, this line is a long wire, about 60cm long running in the terminal; it's very easy to either cut the line and insert a derivation, or use a vampire connector.
- in the same region of the scematic, we easily find a "local ground", the 0V level.
- below the indicator PCB, we find a TEST plug, which includes an other ground pin. This pin has a better gauge than the ground wire of the INDICATOR, and is better suited for ground connections, and supply.
- The indicator PCB include a +5V line, but it comes from a 7805 which are usually rated for 1A or 3A. The Orange pi consumes between 0.2 and 0.3A; and I do not want to load the built-in/manufacturer's 7805 with this if it's designed for 1A. So I am notgoing to supply my opi from here; but on the DIFFUSER plug, there is a 14V pin. Again, I can easily catch this pin, and put a vampire connection on the wire. Conversion from 14V to 5V is done with a "car conversion module 12V-5V micro USB" https://www.amazon.fr/gp/product ... 3_s00?ie=UTF8&psc=1 ... or this model because I prefer the 90° plug (which turns in the good direction for opi) https://www.amazon.fr/Transforma ... Z9VVCEPHQV45B5CMP16 .

Please, pay attention to the fact the "local ground" (aka 0V, the small ground with vertical lines below) is NOT connected to "earth" (yellow wire coming with the live mains, phase and neutral, which uses a triple horizontal lines symbol). This detail is important. And I will do my best to use these words correctly: ground vs earth.

By plugging the pi directly to the IR-DATA line, I can at the same time:
- receive IR frames; the pi will receive exactly the same IR data as the main board of the terminal, disregarding any light condition, reflection, or else. I do catch exactly the same data as the terminal.
- because the IR receiver is iddleing at high state, and pulling the data line down to send data, it's trivial for the pi to also send data on this line, by grabbing the line down when required, and leaving the line floating otherwise.

*** Orange pi startings

I chose the Orange Pi Zero cheapest version 256M, H2+. For SDcard, I had many problems with cheap cards, so let's use good quality ones: Sandisk Ultra white-grey 16GB; a smaller card would be cheaper, but I could not find them anymore. The supply is mentionned above.

I will also use a DS18B20 1W (1wire) temperature probe. They are sold with 1m wires for 2€ on Amazon.

The disk image I used is Armbian_5.35_Orangepizero_Ubuntu_xenial_next_4.13.16.img
https://www.armbian.com/orange-pi-zero/
Note that the version 5.35 will change after update (after running aptitude update / aptitude distupgrade, the version will be updated to 5.38 or more ...).

Also, it's critical to know that this image is using a 4.13.16 Linux kernel. There have been huge changes between line 3 and line 4 kernels, and all other tutorials that were written before mid 2017 are deprecated and unusable on Linux 4.* . The explanations below do not apply to Linux 3.* .

Most people write the disk image using dd; I use a wrapper that does many tricks for me; I will not cover this part here. This tutorial is not about step-by-step complete setup; I will only cover what's specific to my project. But, I let you know that my wrapper does many things: get rid of root password change and user creation, setup fixed IPv4 and IPv6, and setup my VPN; and install many packages (make, unison, gcc, git ...). Before the very first time I log in the pi, all this is already done. I won't either talk about SSH. My wrapper sets-up RSA auth keys, so that I don't have to think about passwords at all.

*** Reading temperature.

This is probably the easiest part.

Edit /boot/armbianEnv.txt and on the line overlays, add the w1-gpio word; and param_w1_pin=PA15 on the next one; it will look like this:

# cat /boot/armbianEnv.txt
verbosity=1
logo=disabled
console=both
disp_mode=1920x1080p60
overlay_prefix=sun8i-h3
overlays=cir usbhost2 usbhost3 w1-gpio
param_w1_pin=PA15
rootdev=UUID=ebe9dacf-124f-486c-b6c1-08749e209374
rootfstype=ext4
usbstoragequirks=0x2537:0x1066:u,0x2537:0x1068:u
Note that this setup is specific to the Orange Pi Zero H2+ (common for 256 and 512), due to the fact we have an additionnal memory chip on the first SPI bus.

On RAspberry Pi forums, I have learnt that 1w requires time critical commands, and that only the SPI bus is able to provide this; so, in short, 1W can be handled only on SPI pins. The oPi has two SPI bus; the first one is dedicated to the memory chip at the back (16MB IIRC), a small bsquare chip with 8 legs on the back of the pi, just in the middle, you can't miss it. Recent opi0 have a larger memory; opi that are not in the 0 range do not have it (and they have only one SPI bus). All this explains why the w1-gpio wrapper can not use the default first bus, and needs to be instructed to use the second SPI bus, the one on PA15. This may vary on other opi0 (the ones which are not H2+ or H3: H5 and other bananas). And it will differ on non 0 pis.

After this modification, you need to reboot. I give all commands in one shot:

# ls /sys/bus/w1/devices/
28-041703225effw1_bus_master1
# cat /sys/bus/w1/devices/28*/w1_slave
4f 01 4b 46 7f ff 0c 10 46 : crc=46 YES
4f 01 4b 46 7f ff 0c 10 46 t=20937
And from an other pi which has two probes:

# ls /sys/bus/w1/devices/
28-0517023468ff28-0517025727ffw1_bus_master1
# cat /sys/bus/w1/devices/28*/w1_slave
8f 00 4b 46 7f ff 0c 10 a0 : crc=a0 YES
8f 00 4b 46 7f ff 0c 10 a0 t=8937
92 00 4b 46 7f ff 0c 10 88 : crc=88 YES
92 00 4b 46 7f ff 0c 10 88 t=9125
A quick conversion into °C :

# cat /sys/bus/w1/devices/28*/w1_slave | grep "t=" | cut -d "=" -f2 | awk '{print $1/1000}'
8.937
9.125

Note that if you have other non DS18B20 1W devices, this quick script will break.

I also remind you that things like 28-0517023468ff28-0517025727ff are world uniq 64b identifiers. You can rely on them; they can not be predicted when you buy a new item. Once you installed a new item on a pi, this identifier will never change.

*** My failed attemps to use lirc-alsa

https://forum.armbian.com/topic/ ... s-on-orangepi-zero/

In that thread, I explain that I have found a bug in mode2, how to use (in theory) an Alsa sound card to read and emit IR frames, and how it all failed for me.

DoubleHP post at 2018-2-18 04:53:20

Edited by DoubleHP at 2018-2-18 23:38

*** IR digression

A classic introduction to the basic NEC protocol; this is how most TV IR remotes work. Every one messing with IR must know this base; but, with my Fujitsu air cooler, things are going to be completely different:
https://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol
The main idea you have to remember is:

[*]A "logical 0" is a 562.5 microsecond pulse, followed by a      562.5 microsecond gap.
[*]A "logical 1" is a 562.5 microsecond pulse, followed by a      1687.5 microsecond gap.
except that for Fujitsu, the basic timing will be 400us instead of 562.

These topics deals about LIRC to read IR; but they are using very old Armbian, not using DTO/DTB, and do not work any more with recent Armbian:
https://forum.armbian.com/topic/ ... receive-irinfrared/
http://codelectron.com/how-to-setup-infrared-remote-control-in-orange-pi-zero-using-lircd-and-python/

Also, whatever you read about RAspberry Pi, they are very lucky people, because they have a working trivial driver called:
irc_dev
lirc_rpi gpio_in_pin=23 gpio_out_pin=22
This is magically simple; but I did not find any equivalent for Orange/Armbian.

I believe that to transmit IR frames, it should be possible to edit the "cir" overlay, and ask him to send frames on a GPIO. But I did not take time to look for the source of the cir overlay.

This guy is doing wonderfull work with pigpio; not usable on orange pi:
http://blog.bschwind.com/2016/05 ... ry-pi-without-lirc/

A very good introduction to Infra-Red:
http://www.instructables.com/id/ ... nditioning-control/
not usable for me; his frames are completely different from Fujitsu. In particular, the checksum works a different way. Panasonic is doing things the traditionnal way (without initial constant, and without decrementing the byte count).

If you configure your pi (or buy an FTDI USB dongle) to set a full serial port, maybe you can ask LIRC to use GPIOs. It is probably possible to ask the chipset to configure a complete UART2, and then, you can use the following tutorial to ask LIRC to use it to transmit data:
https://pmgration.wordpress.com/ ... and-receivers-ftdi/
But this tutorial is not clear about which pins of the FTDI are used for reception and emission; so I did not want to loose time on this, and started writing my own wrapers.

*** Reading infra red

First, add "cir" to the overlays line, and reboot (see above). Now you have a new device node /dev/lirc0 . Install lirc:

aptitude install lirc
As explained in the above link, I lost days on bugs; long story short, I use mode2 to get the raw timings, and wrote a bash script to convert them into bit-stream. This code produces one line on stdout per frame; garbage is sent on stderr. The line is sent in one shot; I used to output bits one by one, but happened to be uncompatible with other features of my project; so the bit's are kept in cache untill end of frame, or timeout.

# cat read_ir.sh
#!/bin/bash

n=1
t=1
v=400
mode2 -d /dev/lirc0 | {
      frame=""
while true ; do
      read -t 1 line
      [ "$line" = "" ] && {
                # timeout
                [ $n -eq 0 ] && {
                        echo "$frame"
                        frame=""
                        n=1
                }
                t=1
                continue
      }
#echo "_ $line _" >&2
      n=0
      # the awk methods works, but creation of awk process takes too much time
      # bash internals are faster
#       typ="$(echo "$line" | awk '{print $1}')"
#       val="$(echo "$line" | awk '{print $2}')"
      array=( $line )
      typ=${array}
      val=${array}
      z=0
#       [ $val -gt $(($v/2)) ] && [ $val -lt $(($v*2)) ] && r=s && z=1
#       [ $val -gt $(($v*2)) ] && [ $val -lt $(($v*4)) ] && r=m && z=1
#       [ $val -gt $(($v*8)) ] && [ $val -lt $(($v*12)) ] && r=l && z=1
      y=$((($val+$v/2)/$v))
      [ $y -eq 1 ] && r=s && z=1
      [ $y -eq 3 ] && r=m && z=1
      [ $y -eq 4 ] && r=l && z=1
      [ $y -eq 7 -o $y -eq 8 -o $y -eq 9 ] && r=x && z=1
      [ $y -gt 20 ] && r=xx && z=1
      [ $z -ne 1 ] && {
                echo ".${val}/$y ???" >&2
                continue
      }
#       echo "$typ $r" >&2
      [ "$typ" = "space" ] && {
                case "$r" in
                        "s")
                              frame="${frame}0"
                              t=0
                              ;;
                        "m")
                              frame="${frame}1"
                              t=0
                              ;;
                        "l")
                              [ $pp = "x" ] && {
                                        # begin of frame
                                        frame="${frame}S"
                              } || {
                                        # error
                                        echo "LARGE" >&2
                              }
                              ;;
                        "x")
                              true
                              echo "???X" >&2
                              ;;
                        "xx")
                              true
                              echo "???Y" >&2
                              ;;
                        *)
                              echo "???T" >&2
                esac
                pp=0
                continue
      }
      # here, we have a pulse.
      case "$r" in
                "s")
                        true
                        ;;
                "l")
                        true
                        ;;
                "x")
                        # begin of frame ?
                        [ $t -eq 0 ] && echo "$frame"
                        [ $t -eq 0 ] && frame=""
                        t=1
                        ;;
                *)
                        echo "???P" >&2
      esac
      pp=$r   # previous pulse
done
}
Do not forget chmod +x ...

This code is completely specific to my project. It will work only with Fujitsu remotes for air cooling systems. I have found some explanations about the frame details in here:
http://www.hifi-remote.com/johnsfine/DecodeIR.html
In very short: the base timing is t=400us. The start sequence is a 8t pulse followed by 4t space. At electronic level, an infra red pulse becomes a 0V shortcut; and a space (absence of light) is when the receiver leaves the IR-DATA lines floating, what means +5V due to the pullup. Then, '0' bit is 1t pulse then 1t space; '1' bit is 1t pulse then 3t space. There is a "stop condition", made of a 1t pulse, followed with a 110t space. The stop condition is always eaten away by LIRC; but it's required to know it exists, when we want to retransmit frames.

DoubleHP post at 2018-2-18 04:53:58

Edited by DoubleHP at 2018-2-18 22:53

*** reverse engineering frames, and producing new frames (the "proxy" part).

Here comes the heavy job.

All frames are not born equal.

There are short and long frames.

Short frames are usually the "turn off" instruction; there is an other case, I forgot if it's "dry coil", or "fan blow".

All frames start with the same sequence. The Hifi-Remote link gives clues about it; the two first bytes (16 bits) are manufacturer identifier ("20:8,99:8,0:4" means decimal '20' writen over 8 bits, decimal '99' written over 8 bits, decimal '0' written over 4 bits - did not understood what E-D-S-F means).

The last byte is a checksum; took me days to understand how it works. You initiate a variable at '111' (decimal), add all received data (all but the last one) bytes in this variable, substract the number of received data bytes (7 for short frames, 15 for long frames). To this result, you apply a modulo 256 (this is equivalent to performading AND 0x11111111), and substract it from 255 (equivalent to XOR 0x11111111).

Note that in the frame, bytes are send as little endian (least significat bit first). So string manipulation is required to reverse this into big endian, which is the expected format for BASH. And of course, the result must again be reverted before being compared to actual received checksum, or sending it.

My code computes two checksums at the same time. Variable CRC6 contains the checksum for the received frame; CRC8 is for transmitted frame (if we need to transmit).

As explained above, this code does many things in one shot:
- I max the temperature at 24°C
- I force the mode to AUTO, and prevent from using heat, cool, or dry mode. I only allow auto and fan mode.

I believe that the protocol could accept termperatures from 16 to 31; I did not try to forge such commands; did not have the patience to wait 3 days and see if the room temp matches the forged command.

The logic is simple:
- in all case, we build the frame to be sent
- if any change is done to any byte, I set the 'resend' variable
- if this variable is set, then, we send the frame

There is a timeout at 5s; every 5s, we check for cache files; if they exist, we send the command. Commands are pre-set frames. This is designed to avoid conflits on emission PGIO pin. This script is the only one which may call transmission program.

Any transmission waits 1s to avoid conflicts with the remote.

I do not detect if the transmitted frame is sent correctly. I could: if next received frame is not identical to last sent frame, then, re-emit it.

To transmit an order, just touch a file matching variable name in /dev/shm .

The following comments have been removed from the next post in order to make the code fit the 10000 char limit:

# Sending the OFF frame at start has several purposes:
# - put the terminal in a known state at boot time
# - switch things off by default; reboots are rare; people can turn it on later if they need.
# - work around the LIRC bug that drpops the last bit of first frame; so that the next signal send by user can be received correctly (and won't fall in the "invalid too short frame" detector).

# bash decimal to binary converter
# https://stackoverflow.com/questions/10278513/bash-shell-decimal-to-binary-conversion

# TODO: reset last_sent to "" after next frame is received

# https://stackoverflow.com/questions/11461625/reverse-the-order-of-characters-in-a-string

# http://www.hifi-remote.com/johnsfine/DecodeIR.html
# https://electronics.stackexchange.com/questions/183785/understanding-ir-protocol-checksum-generation/356781#356781

#[ "${D2B[${crcl6}]}" = "${byte}" ] && echo "CRC6 is good" || echo "CRC6 is bad :/ $(((2#${byte}-$crcl6+256)%256))"
#[ "${D2B[${crcl8}]}" = "${byte}" ] && echo "CRC8 is good" || echo "CRC8 is bad :/ $(((2#${byte}-$crcl8+256)%256))"

DoubleHP post at 2018-2-18 18:51:55

Edited by DoubleHP at 2018-2-18 21:27

.

# cat write_ir.sh
#!/bin/bash

# OFF:
frame_off="S00101000110001100000000000001000000010000100000010111111"

# turn on at +18 @auto
frame_on_auto="S00101000110001100000000000001000000010000111111110010000000011001000010000000000000000000000000000000000000000000000010011110001"

# turn on at +18 @heat
frame_on_heat="S00101000110001100000000000001000000010000111111110010000000011001000010000100000000000000000000000000000000000000000010011010001"
last_sent=""

frame_header="0010100011000110000000000000100000001000"

rm /dev/shm/frame_o*
touch /dev/shm/frame_off

send_frame() {

                echo "S: $lino" >&2
                str=""
                while read -N 1 char
                do
                        [ "$char" = "" ] && break
                        [ "$char" = "S" ] && {
                              str="$l"
                              str="${str}\n$(($s+$m))"
                              continue
                        }
                        str="${str}\n$s"
                        [ $char -eq 0 ] && str="${str}\n$s" || str="${str}\n$m"
                done <<<"$(echo "$lino")"
                # STOP signal
                str="${str}\n$s"
                str="${str}\n$(($s*110))"
                str="${str}\n987654321" # my EOF to kill transmission

                last_sent="${lino}"
                sleep 1
                echo -e ${str} | tcc -lwiringPi -run /root/transmit_ir.c

}

d=-16
s=400
m=$(($s*3+$d))
l=$(($s*8+$d))
s=$(($s+$d))

D2B=({0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1})

while true
do
      read -t 5 line
      [ "$line" = "" ] && {
                # empty frame or timeout
                for i in frame_off frame_on_auto frame_on_heat
                do
                [ -e "/dev/shm/$i" ] && {
                        rm "/dev/shm/$i"
                        lino="${!i}"
                        send_frame
                }
                done
                continue
      }
      echo
      echo "R: $line" >&2

      [ $((${#line})) -lt $((7*8+1)) ] && {
                # a frame must be a least 7 bytes = 42 bits, plus the start 'S'
                echo "Ignoring short/bad frame."
                continue
      }
      [ $((${#line})) -eq $((7*8+1)) -o $((${#line})) -eq $((16*8+1)) ] || {
                # a frame must be a least 7 bytes = 42 bits, plus the start 'S'
               
echo "Ignoring frame: BAD LENGTH: $((${#line})) is neigther $((7*8+1))
or $((16*8+1)) (including start bit 'S')."
                continue
      }

      [ "$last_sent" != "" ] && {
                lastfs="${last_sent:0:$((${#last_sent}-8))}"
                linens="${line:0:$((${#line}-8))}"
                [ "${lastfs}" = "${linens}" ] && {
                        echo "Skipping work on frame we just sent." >&2
                        continue
                }
      }

      
[${#line} -gt $((${#frame_header}+1)) ] && [
"S${frame_header}" != "${line:0:$((${#frame_header}+1))}" ] && {
                echo "Frame header is not designed for us; skipping." >&2
                continue
      }

      lino=""
      ct=0
      byte=""
      byto=""
      byte_flushed=1
      bct=0
      g_on=x
      g_ton=x
      g_off=x
      fan_blow=x
      fan_mode=x
      fan_swing=x
      temp=x
      coil_dry=x
      sleep_s=x
      master=x
      crc6=$((255-142-2))   # compute it as decimal
      crc8=$((111))   # compute it as decimal
      crct6=0
      crct8=0
      resend=0

      while read -N 1 char
      do
                [ "$char" = "" ] && break
                [ "$char" = "S" ] && {
                        [ $ct -eq 0 ] && {
                              lino="S"
                              byte_flushed=1
                              continue
                        } || {
                              echo "Start bit 'S'was found in middle of a line. This is invalid. Skipping." >&2
                              lino=""
                              break
                        }
                }
                ct=$(($ct+1))
                [ $(($ct%8)) -eq 1 ] && bct=$(($bct+1))
                [ $(($ct%8)) -eq 1 -a $ct -ne 1 ] && {
                        docrc=0
                        [ $bct -gt 0 ] && docrc=1
                        [ $docrc -eq 1 ] && crc6=$((${crc6}+(2#${byte})))
                        [ $docrc -eq 1 ] && [ $bct -ne 16 ] && crc6=$((${crc6}-1))
                        [ $docrc -eq 1 ] && crct6=$(($crct6+1))
                #       echo -n " post6. #${byte}b ($((2#${byte}))).$bct. ${crc6} .${crct6}.${docrc}." >&2
                }
                [ $(($ct%8)) -eq 1 -a $ct -ne 1 ] && {
                        docrc=0
                        [ $bct -gt 0 ] && docrc=1
                        [ $docrc -eq 1 ] && crc8=$((${crc8}+(2#${byto})))
                        [ $docrc -eq 1 ] && [ $bct -ne 16 ] && crc8=$((${crc8}-1))
                        [ $docrc -eq 1 ] && crct8=$(($crct8+1))
                #       echo " post8. #${byto}b .$bct. ${crc8} .${crct8}.${docrc}." >&2
                }
                [ $(($ct%8)) -eq 1 ] && {
                        a="$byto"
                        len=${#a}
                        for ((i=0;i<len;i++)); do a=${a:i*2:1}$a; done; a=${a:0:len}
                        lino="${lino}${a}"
                        byte_flushed=1
                        byte=""
                        byto=""
                }
                byte="${char}${byte}"   # reverse order of bits in byte
                byto="${char}${byto}"   # reverse order of bits in byte
                byte_flushed=0
                #echo "$ct: $char" >&2
                [ $ct -eq 48 ] && {
                        [ "$byte" = "00000010" ] && g_off=1 && g_on=0
                        [ "$byte" = "11111110" ] && g_on=1
                        [ "$byte" = "01101100" ] && fan_blow=1 && g_on=X
                }
                [ $ct -eq 72 ] && {
                        temp=${byto:0:4}
                        [ $g_on -eq 1 ] 2>/dev/null && {
                              temp_dec="$((2#${temp}))"       # binary to decimal
                              temp_deco=$temp_dec
                              [ $temp_dec -gt 8 ] && temp_dec=8 && resend=1
                              temp8b=${D2B[${temp_dec}]} # decimal to binray
                              temp4b=${temp8b:4:4}
                              lowbyto=${byto:4:4}
                              byto="${temp4b}${lowbyto}"
                        }
                }
                [ $ct -eq 80 ] && {
                        #master=${byto:5:3}
                        master=${byto:4:4}
                        mastero="$master"
                        # only allow auto or fan
                        #[ "$master" = "000" -o "$master" = "011" ] || {
                        #       master="000"
                        #       resend=1
                        [ "$master" = "0000" -o "$master" = "0110" ] || {
                              master="0000"
                              resend=1
                        }
                        lowbyto=${byto:0:4}
                        byto="${master}${lowbyto}"
                }
                [ $ct -eq 65 ] && g_ton=$char
                [ $ct -eq 76 ] && coil_dry=$char
                [ $ct -eq 85 ] && fan_swing=$char
                        # force it to AUTO: 0000xxxx
                        # coil dry: 00010xxx
                        # sleep: xxx01xxx
                        # leave it untouched
                echo -n "$char" >&2
                [ $(($ct%8)) -eq 0 ] && echo -n "." >&2
      done <<<"$(echo "$line")"
      echo >&2
      crc6=$(($crc6%256))
      crc8=$(($crc8%256))
      crcl6=$((255-($crc6%256)))
      crcl8=$((255-($crc8%256)))

      
[ "${temp}" = "x" ] && temp_dec=0 && tempa=0 &&
tempo=0 && otemp=0 && tempstr="x" ||
tempstr="${temp}/$((${temp_deco}+16))>$((${temp_dec}+16))/${temp4b}"
      [ "${master}" = "000" ] && master="auto"
      [ "${master}" = "001" ] && master="cool"
      [ "${master}" = "010" ] && master="dry"
      [ "${master}" = "011" ] && master="fan"
      [ "${master}" = "100" ] && master="heat"
      [ "${mastero}" = "000" ] && mastero="auto"
      [ "${mastero}" = "001" ] && mastero="cool"
      [ "${mastero}" = "010" ] && mastero="dry"
      [ "${mastero}" = "011" ] && mastero="fan"
      [ "${mastero}" = "100" ] && mastero="heat"
      
echo "Off(${g_off}) On(${g_on}) Ot(${g_ton}) T(${tempstr})
M(${mastero}>${master}) Dry(${coil_dry}) Blow(${fan_blow})
swing($fan_swing) CheckRecomp(${crc6}::${D2B[${crcl6}]}:${crct6})
CheckOutput(${crc8}::${D2B[${crcl8}]}:${crct8}) CheckActual(${byte})"
>&2
      #echo >&2
      [ "$g_off" = "1" -o "$g_on" = "1" -o "${fan_blow}" = "1" ] || {
                echo "Invalid frame; ignoring it ..."
                continue
      }

      [ "${g_off}" = "1" ] && echo "Off short frame. Skipping." && continue
      [ "${fan_blow}" = "1" ] && echo "Blow short frame. Skipping." && continue

      [ $resend -eq 1 ] && {
                [ $byte_flushed -ne 1 ] 2>/dev/null && {
                        a="${D2B[${crcl8}]}"
                        len=${#a}
                        # https://stackoverflow.com/questions/11461625/reverse-the-order-of-characters-in-a-string
                        for ((i=0;i<len;i++)); do a=${a:i*2:1}$a; done; a=${a:0:len}
                        lino="${lino}${a}"
                }

                send_frame
                true
      } || echo "Frame was fine."
      sleep 1
done

DoubleHP post at 2018-2-18 18:52:13

Edited by DoubleHP at 2018-2-18 21:28

*** IR transmission code

Because I have not been able to use irsend (see above; irsend fails on opi; and pigpio does not work on Orange/Armbian), I hd to write my own code to transmit frames. This code transmits

# cat transmit_ir.c

// tcc -lwiringPi -run main.c

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <unistd.h>
#include <wiringPi.h>

int mydelay(long val)
{
      struct timeval t0, t1;
      long elapsed,count, loops;

      elapsed=0;
      gettimeofday(&t0,NULL);
      for(; elapsed<val; )
      {
//            usleep(5);
                gettimeofday(&t1,NULL);
                elapsed = (t1.tv_sec-t0.tv_sec)*1000000 + t1.tv_usec-t0.tv_usec;
      }
      return(0);
}

main()
{
// https://stackoverflow.com/questions/5362577/c-gettimeofday-for-computing-time
      int outled, res, pinstate;
      outled=6;       // 6 = PA2 = pin 22
      outled=7;       // 7 = PA6 = pin 7
      char buf;
      int k;

//      pinstate=INPUT;
      pinstate=LOW;

      wiringPiSetup () ;
      pinMode (outled, OUTPUT);

      usleep(1000);

      while (1) {
      while (res=scanf("%d", &k) ) {
                if(k==987654321) return (0);
                if(pinstate==LOW)
                        pinstate=HIGH;
                else
                        pinstate=LOW;
      //      digitalWrite(i, HIGH);
                digitalWrite(outled, pinstate);
//            digitalWrite(outled, LOW);
                mydelay(k);
      }
      digitalWrite(outled, LOW);
      return (0);
//      pinMode (outled, INPUT);
      }

      return (0);
}

WiringOP: http://www.orangepi.org/orangepi ... =148&extra=&page=15 for details. Installation:

git clone <a class="moz-txt-link-freetext" href="https://github.com/zhaolei/WiringOP">https://github.com/zhaolei/WiringOP</a>
cd WiringOP
chmod +x build
./build
I like tcc because it helps manipulating C code the same way as shell script; or, even closer to python. It allows building at run time; what means, when a project is already running, the code is live rebuild during execution or calling script; changes are immediate during development. I don't care about the performance loss in this case, because tcc is run only a few times per day, and it is run BEFORE the time critical operation, and thus, does not interact with the time critical parts.

This code reads stdin without checking any data validity. You must be 100% cerain of what you are sending.

When changing the pin mode from INPUT to OUTPUT, the chipset always sets the pin value to 0 by default. So, my first idea was to switch the pin mode between INPUT and OUTPUT, so that the pin changed between third state (free floating) and 0V. I found that this mode change takes time, and introduces lags that is not compatible with my timing requirements. If I could use the pin mode to send data, it means, I could directly connect the IR-DATA line to the GPIO, and don't care about anything.

Because I had to keep the pin in OUTPUT mode, it means I had to interface the PGIO with a transistor; thus, the hardware requirement described below.

I initially used PA2, which is in the middle of the port; then, I changed to PA6 to make hardware design easier. PA2 is too close to the SPI port. PA6 allows me to solder a rectangular board that does not take care at all about the SPI port.

In the end, the transmission hardware interface is a simple rectangle, and is easily removable.

There are several possible optimisations:
- replace the first line with soimething like " #!/usr/bin/tcc -lwiringPi -run " to allow running the file directly (see below in other messages)
- since we are doing time critical operations, it would be wise to use isolcpu (see below inside this message)

Selecting the output pin requires to understand the pin association table. It's given by this table:

# gpio readall
+-----+-----+----------+------+---+-Orange Pi+---+---+------+---------+-----+--+
| BCM | wPi |   Name   | Mode | V | Physical | V | Mode | Name   | wPi | BCM |
+-----+-----+----------+------+---+----++----+---+------+----------+-----+-----+
|   |   |   3.3v |      |   |1 || 2|   |      | 5v       |   |   |
|12 |   8 |    SDA.0 | ALT3 | 0 |3 || 4|   |      | 5V       |   |   |
|11 |   9 |    SCL.0 | ALT3 | 0 |5 || 6|   |      | 0v       |   |   |
|   6 |   7 |   GPIO.7 | ALT3 | 0 |7 || 8| 0 | ALT3 | TxD3   | 15| 13|
|   |   |       0v |      |   |9 || 10 | 0 | ALT3 | RxD3   | 16| 14|
|   1 |   0 |   RxD2 | ALT3 | 0 | 11 || 12 | 0 | ALT3 | GPIO.1   | 1   | 110 |
|   0 |   2 |   TxD2 | ALT3 | 0 | 13 || 14 |   |      | 0v       |   |   |
|   3 |   3 |   CTS2 | ALT3 | 0 | 15 || 16 | 0 | ALT3 | GPIO.4   | 4   | 68|
|   |   |   3.3v |      |   | 17 || 18 | 0 | ALT3 | GPIO.5   | 5   | 71|
|64 |12 |   MOSI | ALT3 | 0 | 19 || 20 |   |      | 0v       |   |   |
|65 |13 |   MISO | ALT3 | 0 | 21 || 22 | 0 | ALT3 | RTS2   | 6   | 2   |
|66 |14 |   SCLK | ALT3 | 0 | 23 || 24 | 0 | ALT3 | CE0      | 10| 67|
|   |   |       0v |      |   | 25 || 26 | 0 | ALT3 | GPIO.11| 11| 21|
|19 |30 |    SDA.1 | ALT3 | 0 | 27 || 28 | 0 | ALT3 | SCL.1    | 31| 18|
|   7 |21 |GPIO.21 | ALT3 | 0 | 29 || 30 |   |      | 0v       |   |   |
|   8 |22 |GPIO.22 | ALT3 | 0 | 31 || 32 | 0 | ALT3 | RTS1   | 26| 200 |
|   9 |23 |GPIO.23 | ALT3 | 0 | 33 || 34 |   |      | 0v       |   |   |
|10 |24 |GPIO.24 | ALT3 | 0 | 35 || 36 | 0 | ALT3 | CTS1   | 27| 201 |
|20 |25 |GPIO.25 |OUT | 1 | 37 || 38 | 0 | ALT3 | TxD1   | 28| 198 |
|   |   |       0v |      |   | 39 || 40 | 0 | ALT3 | RxD1   | 29| 199 |
+-----+-----+----------+------+---+----++----+---+------+----------+-----+-----+
| BCM | wPi |   Name   | Mode | V | Physical | V | Mode | Name   | wPi | BCM |
+-----+-----+----------+------+---+-Orange Pi+---+------+----------+-----+-----+
The gpio command is not available in the raw Ubuntu image; it's provided by WiringOP.

*** CPU optimisations.

There are several things to tune for time critical operations.

The easy part (this will hugely increase the power consumption of the pi; it's not an issue for me, because during winter time, they contribute to hgeating the house; during summer time the whole system is turned off completely by a relay):

/usr/bin/cpufreq-set -f 1200MHz
/usr/bin/cpufreq-set -g performance
The tricky part:
https://www.raspberrypi.org/forums/viewtopic.php?f=63&t=200793

Add "isolcpus=3" to the kernel pompt arguments. This will prevent the normal system from using the core #3 (the first core is #0) for normal tasks; then, the core will be dedicated for what we explicitely want to, and will avoid having the core shared between several tasks. Our code will never be interrupted by an other task.

Editing kernel arguments is tricky on OrangePi/Armbian: edit the file /boot/boot.cmd , search for a line looking like

setenv bootargs "root=${rootdev} rootwait rootfstype=${rootfstype} ${consoleargs} hdmi.audio=EDID:0 disp.screen0_output_mode=${disp_mode} panic=10 consoleblank=0 loglevel=${verbosity} ubootpart=${partuuid} ubootsource=${devtype} usb-storage.quirks=${usbstoragequirks} ${extraargs} ${extraboardargs} isolcpus=3"
and run this command which is given in the last comment of the file (about line 44 in write_ir.sh ):

mkimage -C none -A arm -T script -d /boot/boot.cmd /boot/boot.scr
After reboot, type "top", then press the number "1", and you will see an expanded view of your activity; whatever you do, core #3 will always remain around 99% iddle time. A good command to check it and produce load is to open 6 terminals, and run this harmless command in each one: "cat /dev/urandom >/dev/null". Each of them will try tp use 100% CPU; they are started randomly on a core or an other; but now, they will not be started on core #3.

To start the transmission program on the desired core, change the line that calls transmit_ir into this:

echo -e ${str} | /usr/bin/taskset -c 3 tcc -lwiringPi -run /root/transmit_ir.c
Now, tcc and child process should all run on core #3. If you want to make things even cleaner, you may build a definitive binary

tcc -lwiringPi transmit_ir.c -o transmit_ir
and change the call for

echo -e ${str} | /usr/bin/taskset -c 3 /root/transmit_ir

DoubleHP post at 2018-2-18 18:52:31

Edited by DoubleHP at 2018-2-19 04:19

*** IR transmission hardware

This was the easiest part for me to design, and will be the most difficult to explain with words.

I initially wanted to directly wire the output GPIO pin, with the input IR pin of the opi, and to the IR-DATA line; use the INPUT state of GPIO to let the line float (rising +5V due to the pullup), and short circuit it to 0C by only setting the pin as OUTPUT, which by default goes to the value "0", as 0V on this chipset (I have tried many things, there is no way to turn a GPIO to '1' (+5V) at the moment we set a pin as OUTPUT - on this pi). But changing pin mode takes a huge amount of time, and is too slow for IR transmission.

This would have had the advantage of being simple, discrete, and invisible.

... So, I had to keep the pin in OUTPUT mode, and only change the value. Value 0 to leave the data line high, value 1 to grab it down (inverted logic could be possible, in an other use case).

The circuit is trivial: just use one transistor (BC548C; but any similar NPN will do), and a pair of resistors.

http://www.falstad.com/circuit/circuitjs.html?cct=$+1+0.000005+10.20027730826997+50+5+43%0Ar+112+192+208+192+0+1000%0Ar+256+160+256+80+0+10000%0At+224+192+256+192+0+1+0+0+100%0Ag+256+224+256+240+0%0A207+96+192+48+176+0+PA6%0A207+96+224+48+208+0+0V+pin+of+Opi%0A207+288+176+400+160+0+IR-DATA+line+of+air+cooling+terminal%0A207+288+224+384+240+0+0V+line+of+the+air+cooling+terminal%0A207+96+80+48+80+0++5V%0Aw+96+80+256+80+0%0Aw+256+160+256+176+0%0Aw+96+192+112+192+0%0Aw+96+224+256+224+0%0Aw+256+224+256+208+0%0Aw+256+224+288+224+0%0Aw+288+176+256+176+0%0Aw+256+160+96+160+0%0A207+96+160+48+144+0+IR+Input+pin+of+Opi%0Aw+224+192+208+192+0%0A
http://tinyurl.com/y9mr3qjd

The circuit allows to plug an IR receiver like
http://www.dx.com/p/vs1838b-remote-ir-receiving-module-deep-blue-295903#.Wohcl9Uhhpg (or any similar 3 leg IR receiver).

https://drive.google.com/file/d/ ... gJ/view?usp=sharing
https://drive.google.com/file/d/ ... QN/view?usp=sharing
https://drive.google.com/file/d/ ... IM/view?usp=sharing
https://drive.google.com/file/d/ ... Z9/view?usp=sharing
https://drive.google.com/file/d/152A6fHVIC49oB2lctGTbpq5N9so6pXn1/view?usp=sharing
https://drive.google.com/file/d/1cJM2NBzksWe9gh_otD1yocRh6aMXY01B/view?usp=sharing

The top of this PCB has two pins to connect to the IR-DATA and 0V of terminal. This top-hat board can be removed, and the project can be used naked, without the PCB.

The wires of the DS18B20 are soldered on a female header. Do not forget a 4kR or 10kR pullup resistor between data and v+ (here I use +3.3). This way, the temp probe can be attached and detached via a simple plug; be carefull about orientation; there is no notch to prevent mistakes; YOU have to remember that the black wire goes next to the ethernet connector. Transparent heat shrink tube helps making things a bit more rigid, and avoids shortcircuits.

*** execution

To run the project, simply chain the two scripts in a background service:

./read_ir.sh | ./write_ir.sh
To send pre-recorded frames, touch the file corresponding to a template:
- to turn off: touch /dev/shm/frame_off
- to turn on: touch /dev/shm/frame_on_heat

I have planned that external calls may only send frames that are already programmed in write_ir.sh .

This project is a proxy because I catch frames on the fly, and may alter them. And send blocking orders.

It's not possible for me to modify the IR remotes; this global solution solves many problems at the same time.

DoubleHP post at 2018-2-18 20:12:11

Edited by DoubleHP at 2018-2-24 23:42

https://drive.google.com/file/d/ ... ln/view?usp=sharing
https://drive.google.com/file/d/ ... 76/view?usp=sharing
Brown wire goes to the gnd pin of TEST plug. On the red wire of the diffuser I use a vampire connector to catch 14V.

https://drive.google.com/file/d/ ... iz/view?usp=sharing
From the bottom plug which goes to the front LED pannel, the two top wires are 0V and IR-DATA

https://drive.google.com/file/d/ ... YZ/view?usp=sharing
The pi fits in the back part, in the angle; there is a very large room for much more than this. The supply sits in a middle channel, inside the corner. Once every thing is in place, it can all fit the cover; only the probe is kept on the side, so half of it's length can come out of the cover. I am not sure if I will leave the temp probe here; the wall is very cold, and this place is probably not representative of the room temp; I should probably move the probe further away from the wall.

https://drive.google.com/file/d/1HRmirOVJD_XrwEkdpy17fof6EMVe7xNH/view?usp=sharing
Once closed, only the temperature probe is visible.

*** Glitches

I have found a glitch that produces 'nois', 'erratic bits' received by mode2, randomly. When terminal is off, I usually don't have any; when fans are blowing, I get 1 to 5 glitches per second. This introduces errors in frame decoding.

My best try was to remove the supply completelt, and power the opi from a battery; it did not affect the problem at all. Even when the pi was connected to the terminal with only two wires (ground and IR-DATA), I still had the glitches.

... untill I connected the ground to earth.

I do not understand the source of this glitch; and this direct connection is probably a bad idead; a better solution would probably to use an RC filter (R being about 1MR (be carefull to use a 500V resistor; most carbone resistors are 250V, and will arc on the high part of the sin wave around 380V), and C= 0.1uF X2 ('X2' as in "type hix two" aka self healing and 500V certified; not as in "twice")).

Edit: Directly connecting ground (0V output of any transformer) to earth (the green/yellow wire going outside under the garden) is heavily discouraged. Even for just a few seconds. The proper way to do things is to connect them with an X2 capacitor, usually 100nF (0.1uF) ("X2" being a "type", that means, certified for use on live mains 250V~ (400V peak), self healing; a better and more expensive type is called "Y"); and, in parallel to this capacitor, you MUST connect a resistor; it must meet many specifications:
- large enough so that it does not overheat (given 250V, U=RI + P=UI => P=U²/R => P=62500/R; R=62500/P, so a 0.25W resistor must be at least 250KR; 470KR is better)
- small enough to discharge the capacitor within 1s (at most 10s): t=RC => 1=470000C => C=1/470000 => at most 2uF; we usually use between 0.1uF and 1uF
- all X2 and Y capacitors are certified for 400V peak
- most carbone resistors are manufactured for 250V peak; so 400 V peak will damage them; many resistors are manufactured for 500V peak; but I don't know which ones (they are easy to find when you fully read the specs)

So, the most common recommended filter is 100nF or 220nF, in parallel with a pair of 270KR or 470KR (for a total of 540KR or 940KR).

People having mains at 110V can use exactly the same configuration, or simplify and use a single 470KR or 1MR resistor (110V~ gives 190V peak; any classic carbone resistor can do that).

I am talking about classic 1/4W ordinary resistor; becarefull, if you are used to collect used components from old stuff, there exist 1/8W carbone resistors that have not only a lower power rate, but also a lower max voltage. They are significantly smaller that classic resistors. Never use CMS resistors, as they are usually rated for 50V (between 25 and 125V).

*** TODO

- talk about multicast
- anti freeze wrapper

*** Final word

Around this code, there will be other scripts to manage the whole system remotely.

This installation contains 4 terminals; so, this setup is done 4 times. All pi's are connected together via Wi-Fi, and communicate via multicast.

DoubleHP post at 2018-2-18 20:26:14

*** Updates.

Reserved message for future updates.

abdulmoeez post at 2025-4-16 19:21:54

It's which means that amazing together with inspiring. We really enjoy any designs together with whoever should get it all during the post shall be grinning.        tennis recruitment USA

articleonlineki post at 2025-4-28 20:52:42

Fabulous article. That blog post impinges on a whole lot of immediate need conflicts of the contemporary culture. You cannot be uninvolved to help you a lot of these conflicts. It blog post grants ideas and even creative concepts. Highly insightful and even helpful.        meble dla dzieci
page: [1] 2 3 4
View full version: Infrared proxy for aircooling system without LIRC