Sunday, November 18, 2018

Controlling a Yamaha V-RX657 reciever from KODI

After fifteen years of use some of the buttons on the remote for my Yamaha V-RX657 began to work sporadicly. In fact, that happened years ago, even the learning remote have become reluctant in changing the volume. So I wanted a way to control it via KODI.

So I got myself some IR transistors and IR LEDs and breadboarded this circuit:

Image from learn.parallax.com


And while tracking the voltage at VA3 with a Salea logic analyser I got plots like these:



Zooming in on each peak revealed this pattern:



Combining this information and doing a duckduckgo revealed that this were the NEC1 protocol. Carrier is 38 kHz, there are 32 bits of data and a stop bit in each burst. Every burst starts with a 9 ms carrier on followed by 4.5 ms off. A pulse of 564 us followed by 564 us off is a "0". A pulse and 1692 us off is 1 one. And there is a last pulse indicating the end of the burst. Repeating commands, e.g. "Volume Up" can be done by a 40 ms delay followed by a 9 ms carrier on, 2.25 ms off and a stop bit. More repeats are done with a 96 ms delay instead of the original 40 ms delay.

MDCDR    01011110101000011001001101101100
DTVCBL   01011110101000010010101011010101
VOLUP    01011110101000010101100010100111
VOLDOWN  01011110101000011101100000100111
PWRON    01011110101000011011100001000111
PWROFF   01011110101000010111100010000111
TUNER    01011110101000010110100010010111
CD       01011110101000011010100001010111
MUTE     01011110101000010011100011000111
DVD      01011110101000011000001101111100
VAUX     01011110101000011010101001010101
VCR1     01011110101000011111000000001111
DVRVCR2  01011110101000011100100000110111

The PWRON, PWROFF and MUTE buttons did not work on my remote, but I found them on irdb.tk. They had all of the codes.

I then used some eevblog code to transmit this through  a IR LED. The repeats for the volume controls are enough to change the volume 3 dB.


http://www.learningaboutelectronics.com/Articles/LED-driver-circuit.php

#define IRLEDpin  2              //the arduino pin connected to IR LED to ground. HIGH=LED ON
#define BITtime   560            //length of the carrier bit in microseconds 562
//put your own code here - 4 bytes (ADDR1 | ADDR2 | COMMAND1 | COMMAND2)

unsigned long TVON    = 0b00100000110111110001000011101111;

unsigned long MDCDR   = 0b01011110101000011001001101101100;
unsigned long DTVCBL  = 0b01011110101000010010101011010101;
unsigned long VOLUP   = 0b01011110101000010101100010100111;
unsigned long VOLDOWN = 0b01011110101000011101100000100111;
unsigned long PWRON   = 0b01011110101000011011100001000111;
unsigned long PWROFF  = 0b01011110101000010111100010000111;
unsigned long TUNER   = 0b01011110101000010110100010010111;
unsigned long CD      = 0b01011110101000011010100001010111;
unsigned long MUTE    = 0b01011110101000010011100011000111;
unsigned long DVD     = 0b01011110101000011000001101111100;
unsigned long VAUX    = 0b01011110101000011010101001010101;
unsigned long VCR1    = 0b01011110101000011111000000001111;
unsigned long DVRVCR2 = 0b01011110101000011100100000110111;

String serial_command="";

void setup()
{
  Serial.begin(9600);
  Serial.setTimeout(1000);
  IRsetup();//Only need to call this once to setup
}

void IRsetup(void)
{
  pinMode(IRLEDpin, OUTPUT);
  digitalWrite(IRLEDpin, LOW);    //turn off IR LED to start
}

// Ouput the 38KHz carrier frequency for the required time in microseconds
// This is timing critial and just do-able on an Arduino using the standard I/O functions.
// If you are using interrupts, ensure they disabled for the duration.
void IRcarrier(unsigned int IRtimemicroseconds)
{
  for(int i=0; i < (IRtimemicroseconds / 26); i++)
    {
    digitalWrite(IRLEDpin, HIGH);   //turn on the IR LED
    //NOTE: digitalWrite takes about 3.5us to execute, so we need to factor that into the timing.
    delayMicroseconds(9);          //delay for 13us (9us + digitalWrite), half the carrier frequnecy
    digitalWrite(IRLEDpin, LOW);    //turn off the IR LED
    delayMicroseconds(9);          //delay for 13us (9us + digitalWrite), half the carrier frequnecy
    }
}

//Sends the IR code in 4 byte NEC format
void IRsendCode(unsigned long code)
{
  //send the leading pulse
  IRcarrier(9000);            //9ms of carrier
  delayMicroseconds(4500);    //4.5ms of silence

  //send the user defined 4 byte/32bit code
  for (int i=0; i<32; i++)
    {
    IRcarrier(BITtime);   //turn on the carrier for one bit time
    if (code & 0x80000000)  //get the current bit by masking all but the MSB
      delayMicroseconds(3 * BITtime); //a HIGH is 3 bit time periods
    else
      delayMicroseconds(BITtime);
     code<<=1;
    }
  IRcarrier(BITtime);  //STOP bit.
}

void loop()
{
 serial_command="";
 if (Serial.available()>0)
 {
  serial_command = Serial.readString();
  if (serial_command.indexOf("PWRON") == 0)
  {
    IRsendCode(PWRON);
    delay(500);
  }
  else if (serial_command.indexOf("PWROFF") == 0)
  {
    IRsendCode(PWROFF);
    delay(500);
  }
  else if (serial_command.indexOf("TUNER") == 0)
  {
    IRsendCode(TUNER);
    delay(500);
  }
  else if (serial_command.indexOf("CD") == 0)
  {
    IRsendCode(CD);
    delay(500);
  }
  else if (serial_command.indexOf("MUTE") == 0)
  {
    IRsendCode(MUTE);
    delay(500);
  }
  else if (serial_command.indexOf("DVD") == 0)
  {
    IRsendCode(DVD);
    delay(500);
  }
  else if (serial_command.indexOf("VAUX") == 0)
  {
    IRsendCode(VAUX);
    delay(500);
  }
    else if (serial_command.indexOf("VCR1") == 0)
  {
    IRsendCode(VCR1);
    delay(500);
  }
  else if (serial_command.indexOf("DVRVCR2") == 0)
  {
    IRsendCode(DVRVCR2);
    delay(500);
  }
  else if (serial_command.indexOf("MDCDR") == 0)
  {
    IRsendCode(MDCDR);
    delay(500);
  }
  else if (serial_command.indexOf("DTVCBL") == 0)
  {
    IRsendCode(DTVCBL);
    delay(500);
  }
  else if (serial_command.indexOf("VOLUP") == 0)
  {
    IRsendCode(VOLUP);
    delay(40);
    IRcarrier(9000);
    delay(2.25);//2.23
    IRcarrier(BITtime);
    delay(96.28);
    IRcarrier(9000);
    delay(2.23);
    IRcarrier(BITtime);
        delay(96.28);
    IRcarrier(9000);
    delay(2.25);//2.23
    IRcarrier(BITtime);
        delay(96.28);
    IRcarrier(9000);
    delay(2.23);
    IRcarrier(BITtime);
        delay(96.28);
    IRcarrier(9000);
    delay(2.23);
    IRcarrier(BITtime);
        delay(96.28);
    IRcarrier(9000);
    delay(2.23);
    IRcarrier(BITtime);
        delay(96.28);
    IRcarrier(9000);
    delay(2.23);
    IRcarrier(BITtime);
    }
  else if (serial_command.indexOf("VOLDOWN") == 0)
  {
    IRsendCode(VOLDOWN);
    delay(40);
    IRcarrier(9000);
    delay(2.23);
    IRcarrier(BITtime);
    delay(96.28);
    IRcarrier(9000);
    delay(2.23);
    IRcarrier(BITtime);
        delay(96.28);
    IRcarrier(9000);
    delay(2.23);
    IRcarrier(BITtime);
        delay(96.28);
    IRcarrier(9000);
    delay(2.23);
    IRcarrier(BITtime);
        delay(96.28);
    IRcarrier(9000);
    delay(2.23);
    IRcarrier(BITtime);
        delay(96.28);
    IRcarrier(9000);
    delay(2.23);
    IRcarrier(BITtime);
        delay(96.28);
    IRcarrier(9000);
    delay(2.23);
    IRcarrier(BITtime);
    }
   else if (serial_command.indexOf("TVON") == 0)
  {
    IRsendCode(TVON);
    delay(40);
  }
 }
}

I used an old Arduino Nano and the 5V USB source on that board to power the LED.

I then put the IR LED in front of the IR receiver on the amplifier.



Connecting to the '/dev/ttyUSB0' port gave me a lot of trouble, it turned out that I had to wait 4 seconds from opening the port to actually using it. This made it clear that I had to create a system with a server controlling the serial port.


#! /usr/bin/env python2
# -*- coding: utf-8 -*-


import socket, sys, signal, serial, time
bytesize=8
parity='N'
stopbits=1
timeout=3
port_name = '/dev/ttyUSB0'
ser = serial.Serial(port_name, baudrate=9600, bytesize=bytesize, parity=parity, stopbits=stopbits, timeout=timeout)
time.sleep(4)
#print(ser.name) #which port



SOCKET_TIMEOUT = 1

sigterm_exit = False
def exit_check(_signo, _stack_frame):
    global sigterm_exit
    sigterm_exit = True

signal.signal(signal.SIGINT, exit_check)
signal.signal(signal.SIGTERM, exit_check)

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.settimeout(SOCKET_TIMEOUT)
sock.bind(('',31442))

while not sigterm_exit:
    try:
        msg, addr = sock.recvfrom(1024)
        print time.ctime(), msg
        ser.write(msg)
        ser.flush()
    except socket.timeout:
        pass
    except socket.error: # prob the SIGINT, sigterm_exit is True now
        pass
ser.close()
print 'done'

This is a UDP server, listening at port 31442. It just relays commands to the serial port.

The final steps is taken in KODI.

At startup KODI now selects the sound channel KODI is playing through. This is the file 'autoexec.py' in '.kodi/userdata'.


import socket
import xbmc
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
addr = ("127.0.0.1", 31442)
sock.sendto('DTVCBL', addr) #this is the channel for kodi/youtube
sock.close()

xbmc.executebuiltin('ActivateWindow(Videos, Files)')


The last change were create a keymap file in '.kodi/userdata/keymaps'

<keymap>
  <global>
    <keyboard>
      <numpadminus>RunScript(/home/pwr/.kodi/userdata/custom_IR.py, VOLDOWN)</numpadminus>
      <numpadplus>RunScript(/home/pwr/.kodi/userdata/custom_IR.py, VOLUP)</numpadplus>
      <numpadtimes>RunScript(/home/pwr/.kodi/userdata/custom_IR.py, DTVCBL)</numpadtimes>
    </keyboard>
  </global>
  <FullscreenVideo>
    <keyboard>
      <numpadminus>RunScript(/home/pwr/.kodi/userdata/custom_IR.py, VOLDOWN)</numpadminus>
      <numpadplus>RunScript(/home/pwr/.kodi/userdata/custom_IR.py, VOLUP)</numpadplus>
      <numpadtimes>RunScript(/home/pwr/.kodi/userdata/custom_IR.py, DTVCBL)</numpadtimes>
    </keyboard>
  </FullscreenVideo>
</keymap>


Thursday, October 18, 2018

Logging tellsticknet output to a database on ubuntu 18.04LTS

I have a wind sensor on the outside of my house, but it is not placed in an optimal location. So I decided to get some data from the sensor and try to correlate it with the meteorological forecast.

The MySQLdb package is used for MySQL access. Create a MySQL user and database named "weather_logger". My current tables are listed as python comments below.


The script listens for the sigterm signal from systemctl and has some error handlers for network failures. It will write information from these events into the "error_logger" table.

The approach is based on this older post.  It assumes one tellsticknet unit in the network. If there are more, replace the tellsticknet discovery string '<broadcast>'  with the ip address of the tellsticknet unit. It does not care about the rolling code (id) of the sensors as well, even if it puts this information into the MySQL database.

The optimal approach for this script could be to remove all sensor knowledge and just put every string collected into the "unknown_logger" table for others to process.

This script is run at startup of the system by a systemd file, "/lib/systemd/system/tellsticknet.service".



[Unit]
Description=Read oregon sensorvalues from tellsticknet and save in mysql db
After=multi-user.target

[Service]
Type=idle
ExecStart=/usr/bin/python /home/<myuser>/tellsticknet_service/tellsticknet.py

[Install]
WantedBy=multi-user.target



(html by hilite.me)

#! /usr/bin/env python2
# -*- coding: utf-8 -*-



import socket
from datetime import timedelta, datetime
import time, sys, os
import MySQLdb
import signal

from sensors import *


sigterm_exit = False
def exit_check(_signo, _stack_frame):
 global sigterm_exit
 sigterm_exit = True




#   CREATE TABLE `weather_logger`.`temperature_logger` ( `ID` INT NOT NULL AUTO_INCREMENT,
#   `sensorID` INT UNSIGNED NULL DEFAULT NULL , `datetime` DATETIME NULL DEFAULT NULL ,
#   `temperature` FLOAT NULL DEFAULT NULL , `humidity` FLOAT NULL DEFAULT NULL,
#   `battery` TINYINT NULL DEFAULT NULL, PRIMARY KEY (`ID`)) ENGINE = InnoDB;

#   CREATE TABLE `weather_logger`.`wind_logger` ( `ID` INT NOT NULL AUTO_INCREMENT,
#   `sensorID` INT UNSIGNED NULL DEFAULT NULL , `datetime` DATETIME NULL DEFAULT NULL ,
#   `wind` FLOAT NULL DEFAULT NULL , `gust` FLOAT NULL DEFAULT NULL, `direction` FLOAT NULL DEFAULT NULL,
#   `battery` TINYINT NULL DEFAULT NULL, PRIMARY KEY (`ID`)) ENGINE = InnoDB;

#   CREATE TABLE `weather_logger`.`unknown_logger` ( `ID` INT NOT NULL AUTO_INCREMENT,
#   `datetime` DATETIME NULL DEFAULT NULL , `data` VARCHAR(2048) NULL DEFAULT NULL,
#   PRIMARY KEY (`ID`)) ENGINE = InnoDB;

#   CREATE TABLE `weather_logger`.`error_logger` ( `ID` INT NOT NULL AUTO_INCREMENT,
#   `datetime` DATETIME NULL DEFAULT NULL , `data` VARCHAR(2048) NULL DEFAULT NULL,
#   PRIMARY KEY (`ID`)) ENGINE = InnoDB;

#
#   CREATE TABLE `weather_logger`.`raw` ( `ID` INT NOT NULL AUTO_INCREMENT,
#   `datetime` DATETIME NULL DEFAULT NULL , `raw_data` VARCHAR(1024) NULL DEFAULT NULL,
#   PRIMARY KEY (`ID`)) ENGINE = InnoDB;


signal.signal(signal.SIGINT, exit_check)
signal.signal(signal.SIGTERM, exit_check)

MYSQLHOST = '10.0.0.7'

SOCKET_TIMEOUT = timedelta(seconds=5)
REG_INT = timedelta(minutes = 10)



db = MySQLdb.connect(host=MYSQLHOST, user="weather_logger",
 passwd="weather_logger",  db="weather_logger")
cur = db.cursor()

date_t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
sql = 'insert into error_logger values(NULL,\"%s\", \"%s\")'%(date_t, "Starting tellsticknet logging")
cur.execute(sql)
db.commit()


sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.settimeout(SOCKET_TIMEOUT.seconds)
sock.bind(('',42314))

comm_ok = False
while not comm_ok:
 if sigterm_exit:
  db.close()
  sock.close()
  sys.exit(0)
 try:
  sock.sendto(b'D', ('<broadcast>', 30303))
  data, (tellsticknetip, port) = sock.recvfrom(2048)
  comm_ok = True
 except socket.error: # power out? try again:
  date_t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  sql = 'insert into error_logger values(NULL,\"%s\", \"%s\")'%(date_t, "No connection with TellstickNet from init")
  cur.execute(sql)
  db.commit()

last_reg_listener = datetime.now() - REG_INT
while not sigterm_exit:
 if datetime.now() - last_reg_listener > REG_INT:
  comm_ok = False
  while not comm_ok:
   if sigterm_exit:
    db.close()
    sock.close()
    sys.exit(0)
   try:
    sock.sendto("B:reglistener", (tellsticknetip, 42314))
    comm_ok = True
   except:
    date_t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    sql = 'insert into error_logger values(NULL,\"%s\", \"%s\")'%(date_t, "No connection with TellstickNet from reglistener")
    cur.execute(sql)
    db.commit()
  last_reg_listener = datetime.now()
 try:
  data,(address, port) = sock.recvfrom(2048)
  if (data.split(":")[6][6:10]=="F824"):
   (rc, temp,hum, batt) = decodeF824(data.split(":")[7][5:5+14])
   date_t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
   sql = 'insert into temperature_logger values(NULL,%d,\"%s\", %f,%f, %d)'%(rc, date_t, temp, hum, int(batt))
   #print(sql)
   cur.execute(sql)
   db.commit()
  elif (data.split(":")[6][6:10]=="1984"):
   (rc, w, wg, direction, batt) = decode1984(data.split(":")[7][5:5+16])
   date_t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
   sql = 'insert into wind_logger values(NULL,%d,\"%s\", %f,%f,%f, %d)'%(rc, date_t, w, wg, direction, int(batt))
   cur.execute(sql)
   db.commit()
  else:
   date_t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
   sql = 'insert into unknown_logger values(NULL,\"%s\", \"%s\")'%(date_t, data)
   cur.execute(sql)
   db.commit()
 except socket.timeout: # time out, try again
  pass
 except socket.error: #power fail?
  date_t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  if sigterm_exit:
   sql = 'insert into error_logger values(NULL,\"%s\", \"%s\")'%(date_t, "sigterm recived")
   cur.execute(sql)
   db.commit()
   break
  else:
   sql = 'insert into error_logger values(NULL,\"%s\", \"%s\")'%(date_t, "No connection with TellstickNet from main loop")
   cur.execute(sql)
   db.commit()
 except:
  date_t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  sql = 'insert into error_logger values(NULL,\"%s\", \"%s\")'%(date_t, sys.exc_info()[0])
  cur.execute(sql)
  db.commit()


#got SIGTERM
db.close()
sock.close()


The "sensors.py" file used:


#some sensors from telldus-core/service/
# this code depends on signal checking done in the tellsticknet
#
#

def decode2914(inp):
 #source:telldus-core/service/ProtocolOregon.cpp@c9567f
 #// rain
 value = int(inp, 16)

 messageChecksum1 = value & 0xF
 value  = value >> 4
 messageChecksum2 = value & 0xF

 value  = value >> 4
 totRain1 = value & 0xF
 value  = value >> 4
 totRain2 = value & 0xF
 value  = value >> 4
 totRain3 = value & 0xF
 value  = value >> 4
 totRain4 = value & 0xF
 value  = value >> 4
 totRain5 = value & 0xF
 value  = value >> 4
 totRain6 = value & 0xF
 value  = value >> 4
 rainRate1 = value & 0xF
 value  = value >> 4
 rainRate2 = value & 0xF
 value  = value >> 4
 rainRate3 = value & 0xF
 value  = value >> 4
 rainRate4 = value & 0xF

 value  = value >> 4
 battery = value & 0xF #// PROBABLY battery
 value  = value >> 4
 rollingcode = ((value >> 4) & 0xF) + (value & 0xF)
 checksum =    ((value >> 4) & 0xF) + (value & 0xF)
 value  = value >> 8
 channel = value & 0xF
 checksum = checksum + totRain1 + totRain2 + totRain3 + totRain4 + totRain5 + totRain6 +\
 rainRate1 + rainRate2 + rainRate3 + rainRate4 +\
 battery + channel + 0x2 + 0x9 + 0x1 + 0x4

 if (((checksum >> 4) & 0xF) != messageChecksum1 or (checksum & 0xF) != messageChecksum2):
  #// checksum error
  return ""

 totRain = ((totRain1 * 100000) + (totRain2 * 10000) + (totRain3 * 1000) +\
 (totRain4 * 100) + (totRain5 * 10) + totRain6)/1000.0*25.4
 rainRate = ((rainRate1 * 1000) + (rainRate2 * 100) + (rainRate3 * 10) + rainRate4)/100.0*25.4

 #std::stringstream retString;
 #retString << "class:sensor;protocol:oregon;model:2914;id:" << static_cast<int>(rollingcode)
 # << ";raintotal:" << std::fixed << std::setprecision(1) << totRain
 # << ";rainrate:" << std::fixed << std::setprecision(1) << rainRate << ";";
 #return "%d\t%f\t%f"%(rollingcode, totRain, rainRate)
 return [rollingcode, totRain, rainRate, battery]




def decode1994(inp):
 #source:telldus-core/service/ProtocolOregon.cpp@c9567f
 #wind
 value = int(inp, 16)
 crcCheck = value & 0xF
 value  = value >> 4
 messageChecksum1 = value & 0xF
 value  = value >> 4
 messageChecksum2 = value & 0xF

 value  = value >> 4
 avg1 = value & 0xF
 value  = value >> 4
 avg2 = value & 0xF
 value  = value >> 4
 avg3 = value & 0xF
 value  = value >> 4
 gust1 = value & 0xF
 value  = value >> 4
 gust2 = value & 0xF
 value  = value >> 4
 gust3 = value & 0xF
 value  = value >> 4
 unknown1 = value & 0xF
 value  = value >> 4
 unknown2 = value & 0xF
 value  = value >> 4
 direction = value & 0xF

 value  = value >> 4
 battery = value & 0xF  #// PROBABLY battery
 value  = value >> 4
 rollingcode = ((value >> 4) & 0xF) + (value & 0xF)
 checksum =    ((value >> 4) & 0xF) + (value & 0xF)
 value  = value >> 8
 channel = value & 0xF
 checksum = checksum + unknown1 + unknown2 + avg1 + avg2 + avg3 + gust1 + gust2 + gust3 + direction + battery + channel

 checksum = checksum + 0x1 + 0x9 + 0x9 + 0x4

 if (((checksum >> 4) & 0xF) != messageChecksum1 or (checksum & 0xF) != messageChecksum2):
  #// checksum error
  return ""

 avg = ((avg1 * 100) + (avg2 * 10) + avg3)/10.0
 gust = ((gust1 * 100) + (gust2 * 10) + gust3)/10.0
 directiondegree = 22.5 * direction

 #retString << "class:sensor;protocol:oregon;model:1984;id:" << static_cast<int>(rollingcode)
 # << ";winddirection:" << directiondegree
 # << ";windaverage:" << std::fixed << std::setprecision(1) << avg
 # << ";windgust:" << std::fixed << std::setprecision(1) << gust << ";";

 #return '%d\t%f\t%f\t%f'%(rollingcode, avg, gust, directiondegree)
 return [rollingcode, avg, gust, directiondegree, battery]


def decode1984(inp):
 #source:telldus-core/service/ProtocolOregon.cpp@c9567f
 #// wind
 value = int(inp, 16)
 crcCheck = value & 0xF
 value  = value >> 4
 messageChecksum1 = value & 0xF
 value  = value >> 4
 messageChecksum2 = value & 0xF

 value  = value >> 4
 avg1 = value & 0xF
 value  = value >> 4
 avg2 = value & 0xF
 value  = value >> 4
 avg3 = value & 0xF
 value  = value >> 4
 gust1 = value & 0xF
 value  = value >> 4
 gust2 = value & 0xF
 value  = value >> 4
 gust3 = value & 0xF
 value  = value >> 4
 unknown1 = value & 0xF
 value  = value >> 4
 unknown2 = value & 0xF
 value  = value >> 4
 direction = value & 0xF

 value  = value >> 4
 battery = value & 0xF  #// PROBABLY battery
 value  = value >> 4
 rollingcode = ((value >> 4) & 0xF) + (value & 0xF)
 checksum =    ((value >> 4) & 0xF) + (value & 0xF)
 value  = value >> 8
 channel = value & 0xF
 checksum = checksum + unknown1 + unknown2 + avg1 + avg2 + avg3 + gust1 + gust2 + gust3 + direction + battery + channel

 checksum = checksum + 0x1 + 0x9 + 0x8 + 0x4

 if (((checksum >> 4) & 0xF) != messageChecksum1 or (checksum & 0xF) != messageChecksum2):
  #// checksum error
  return ""

 avg = ((avg1 * 100) + (avg2 * 10) + avg3)/10.0
 gust = ((gust1 * 100) + (gust2 * 10) + gust3)/10.0
 directiondegree = 22.5 * direction

 #retString << "class:sensor;protocol:oregon;model:1984;id:" << static_cast<int>(rollingcode)
 # << ";winddirection:" << directiondegree
 # << ";windaverage:" << std::fixed << std::setprecision(1) << avg
 # << ";windgust:" << std::fixed << std::setprecision(1) << gust << ";";

 #return '%d\t%f\t%f\t%f'%(rollingcode,avg, gust, directiondegree)
 return [rollingcode, avg, gust, directiondegree, battery]


def decodeF824(inp):
 #source:telldus-core/service/ProtocolOregon.cpp@c9567f
 value = int(inp, 16)
 crcCheck = value & 0xF
 value = value>>4
 messageChecksum1 = value & 0xF
 value = value >>4
 messageChecksum2 = value & 0xF
 value = value >> 4
 unknown = value & 0xF
 value = value >> 4
 hum1 = value & 0xF
 value = value >> 4
 hum2 = value & 0xF
 value = value >> 4
 neg = value & 0xF
 value = value >> 4
 temp1 = value & 0xF
 value = value >> 4
 temp2 = value & 0xF
 value = value >> 4
 temp3 = value & 0xF
 value = value >> 4
 battery = value & 0xF
 value = value >> 4
 rollingcode = ((value >> 4) & 0xF) + (value & 0xF)
 checksum = ((value >> 4) & 0xF) + (value & 0xF)
 value = value >> 8
 channel = value & 0xF
 checksum += unknown + hum1 + hum2 + neg + temp1 + temp2 + temp3 + battery + channel + 0xF + 0x8 + 0x2 + 0x4
 if ((((checksum >> 4) & 0xF) != messageChecksum1) or ((checksum & 0xF) != messageChecksum2)):
  return ""

 temperature = ((temp1 * 100) + (temp2 * 10) + temp3)/10.0
 if (neg):
  temperature = -temperature
 humidity = (hum1 * 10.0) + hum2
 #retStr = "class:sensor;protocol:oregon;model:F824;id:"  #14;temp:0.0;humidity:46;
 #retStr = '%s%d;temp:%1.1f;humidity:%d;'%(retStr,rollingcode,temperature,humidity)
 #return retStr
 #return '%d\t%f\t%f'%(rollingcode, temperature, humidity)
 return [rollingcode, temperature, humidity, battery]




def decode1A2D(inp):
    #source:telldus-core/service/ProtocolOregon.cpp
    value = int(inp,16)

    checksum2 = value & 0xFF
    value = value>>8
    checksum1 = value & 0xFF
    value = value>>8
    checksum = ((value >> 4) & 0xF) + (value & 0xF)
    hum1 = value & 0xF;
    value >>= 8;
    checksum = checksum + ((value >> 4) & 0xF) + (value & 0xF)
    neg = value & (1 << 3)
    hum2 = (value >> 4) & 0xF;
    value >>= 8
    checksum = checksum + ((value >> 4) & 0xF) + (value & 0xF);
    temp2 = value & 0xF;
    temp1 = (value >> 4) & 0xF;
    value >>= 8;
    checksum = checksum + ((value >> 4) & 0xF) + (value & 0xF)
    temp3 = (value >> 4) & 0xF;
    value >>= 8
    checksum = checksum + ((value >> 4) & 0xF) + (value & 0xF)
    rollingcode = value & 0xFF;
    value >>= 8;
    checksum = checksum + ((value >> 4) & 0xF) + (value & 0xF)
    channel = (value >> 4) & 0x7;
    battery = value&0xF #time will tell
    checksum = checksum +  0x1 + 0xA + 0x2 + 0xD - 0xA;

    if (checksum != checksum1):
        return "No";

    temperature = ((temp1 * 100) + (temp2 * 10) + temp3)/10.0;
    if (neg): temperature = -temperature
    humidity = (hum1 * 10.0) + hum2
    #print "temp/hum ", temperature, "/", humidity

    return [rollingcode, temperature, humidity, battery]

Sunday, September 30, 2018

VNC connections to ubuntu 18.04LTS

After installing x11vnc there were the dreaded "unsupported security types" error message.


I tried several things, including disabling the "wayland vnc server" in " /etc/gdm3/custom.conf".

Nothing worked, in the end I just installed "lightdm" instead of the "gdm3". And after enabling screen sharing in the gui settings I had my vnc connection up and running again.




Monday, May 7, 2018

EdgeRouter X VPN connection to mullvad.net

UPDATED here.

Backup your original settings by entering the gui interface, select the system tab in the lower part of the window and choose "Back Up Config"

Copy these files that you obtained from mullvad.net to the edgeos router:

ubnt@ubnt:~$ ls -l /config/auth/
total 20
-rw-------    1 root     vyattacf      6296 May  7 13:38 mullvad_ca.crt
-rw-------    1 ubnt     vyattacf      2202 May  7 13:54 mullvad_crl.pem
-rw-------    1 ubnt     vyattacf       500 May  7 14:16 mullvad_no.conf
-rw-------    1 ubnt     vyattacf        19 May  7 14:09 mullvad_userpass.txt
ubnt@ubnt:~$

Here I am using the "mullvad_no.conf", but you should replace that with the location you are using.

Edit the "mullvad_no.conf" file and insert "/config/auth" before all mullvad filenames (line 21-23 for me)

auth-user-pass /config/auth/mullvad_userpass.txt
ca /config/auth/mullvad_ca.crt
crl-verify /config/auth/mullvad_crl.pem

Now, ***EDIT*** the following text to your settings:
#openvpn to mullvad

set interfaces openvpn vtun0 config-file /config/auth/mullvad_no.conf
set interfaces openvpn vtun0 description 'Mullvad VPN'
set interfaces openvpn vtun0 enable

set service nat rule 5000 description MVPN
set service nat rule 5000 log disable
set service nat rule 5000 outbound-interface vtun0
set service nat rule 5000 source address 10.0.0.0/24
set service nat rule 5000 type masquerade

set service nat rule 5001 description default
set service nat rule 5001 log disable
set service nat rule 5001 outbound-interface eth4
set service nat rule 5001 source address 10.0.0.0/24
set service nat rule 5001 type masquerade

set protocols static table 1 interface-route 0.0.0.0/0 next-hop-interface vtun0

set firewall modify mullvad_route rule 10 description 'MVPN'
set firewall modify mullvad_route rule 10 source address 10.0.0.0/24
set firewall modify mullvad_route rule 10 modify table 1

set interfaces switch switch0 firewall in modify mullvad_route

Changes would probably be in line 3,10,11,16 and 22, outlined in blue. My router are using 10.0.0.1 as the LAN address, eth4 connected to my ISP.

On the edgeos router enter the command  "configure" and paste the edited text into the terminal.

Then enter the "commit" and "save" commands. Browse to your router web interface and refresh to observe the difference.

And check the am.i.mullvad link...

Saturday, January 6, 2018

WAN IP address from Ubiquiti EdgeRouter ER-X

I got myself a new router, an Ubiquiti EdgeRouter ER-X , and when I tried to apply my old methods from the WNR2000v5 I discovered that the web pages returned were full of javascript. I could not find any WAN values in the files.

And that was just as well, the edge router features a full OS, with python pre installed! I set it up with eth4 as the WAN port.

The only caveat was the

show interfaces ethernet eth4 brief

did not work in a script. The command

/opt/vyatta/bin/vyatta-op-cmd-wrapper show interfaces ethernet eth4 brief

has to be used for it to work.

So after setting it up to run via crontab with the command

sudo -e crontab
the script were up and running.

The working python script:

#!/usr/bin/python

import re, os, ftplib, pickle, StringIO

def touch(fname, times=None):
        with open(fname, 'a'):
                        os.utime(fname, times)

cmd_path = '/opt/vyatta/bin/vyatta-op-cmd-wrapper'
cmd_res = os.popen(cmd_path+ ' show interfaces ethernet eth4 brief')
data = cmd_res.read()
cmd_res.close()

# Edgeos show interface command returns
# eth4        xxx.xxx.xxx.xxx
p = re.compile('eth4(\s*)(\d*).(\d*).(\d*).(\d*)')
m = p.search(data)

#m.groups()[0] is white space
if (m and len(m.groups()) == 5):
        current_ip = (m.groups()[1], m.groups()[2], m.groups()[3], m.groups()[4])
        try:
                last_ip = pickle.load(open('/home/user/myip/lastip', "rb" ))
        except IOError:
                last_ip = (1,2,3,4)

        if current_ip != last_ip:
                pickle.dump(current_ip,open('/home/user/myip/lastip', "wb" ))
                data ='<html><head><title>My IP</title></head><b>My IP:\
%d//%d//%d//%d</b></font></p></body></html>\
'%(int(m.groups()[1]),int(m.groups()[2]),int(m.groups()[3]),int(m.groups()[4]))
                output = StringIO.StringIO(data)
                ftp = ftplib.FTP('ftp.server.org', 'username','password')
                ftp.cwd('pub')
                ftp.storlines('STOR index.html', output)
                ftp.close()
        else:
                touch('/home/user/myip/lastip')

Monday, September 18, 2017

Setup realvnc server on raspberry headless using tightvncviewer

When I tried to connect to my headless raspberry via tightvncviewer I got the following message:






It appears that you have to install a viewer from realvnc ($? or registration?) in order to connect.


So I logged in to the raspberry via ssh and started a virtual vnc screen:



vncserver-virtual -Encryption PreferOn -Authentication VncAuth
This starts a new, virtual vnc server with the standard vnc authentications, tightvncviewer can connect to this one.

From this vnc connection I started a realvncviewer session to "localhost"


Allowing me to change the options for the default vnc server connected to the "screen" (port 5900 or :0) on the raspberry.



Accessing the options on the :0 realvnc server via realvnc viewer from a virtual (:1) realvncviewer from tightvncviewer.


From the options change Security/Authentication from "UNIX password" to "VNC password".



Now it is possible to connect directly to the realvncserver from tightvncviewer.




Thursday, January 12, 2017

Fast forwarding the std::next_permutation algorithm

I started my computer with a project that looks more and more like Sisyfos work.

It is trying to solve a puzzle by running through a set of permutations, and it is using std::next_permutation.

However, a day into the computation I realized that the algorithm could be further improved. Should I stop the calculation and restart it? Or let it continue, with the ineffective algorithm?

Looking at the internet I found an in depth explanation of std::next_permutation Implementation Explanation. std::next_permutation will from a starting point increase the permutation in an lexical fashion until it rolls over and returns false:

std::vector<int> skip_next_list = { 1,2,3,4, 5, 6, 7, 8, 9, 10, 11};
do {
//some work here
} while (std::next_permutation(skip_next_list.begin(), skip_next_list.end()));


My search had reached 309000000 when I aborted the job, so I needed to fast forward to that point. I realized that for 6 iterations (3!) it would update the last three entries of skip_next_list, for 24 (4!) the last four and so on.

So this is the skip_next_permutation function, fast forwarding to a number far into the sequence of std::next_permutation.

template<typename It> void skip_next_permutation(uint64_t skip_num, It begin, It end)
{
 if (skip_num <= 0) return; // does not have to be an error

 //build a lut of factorials, this could be a static
 std::vector<uint64_t> factorials;
 factorials.push_back(1); ///  0! == 1
 long idx = 1;
 long input_length = end - begin;
 do {
  factorials.push_back(factorials[idx-1] * idx);
 } while (factorials[++idx - 1] <= skip_num); //

 // now start to loop, find the factorial that is one smaller than the number we want to skip forward
 long my_inc = factorials.size() - 2; // always 2 lower than factorials.size()

 do
 {
  int quotient = skip_num / factorials[my_inc];
  skip_num = skip_num%factorials[my_inc]; //the remaider are the remaining skip_num

  if (skip_num == 0 && quotient == 0) goto end_loop;
  std::swap(begin[input_length - my_inc - 1], begin[input_length - my_inc + quotient - 1]);
  std::sort(begin + input_length - my_inc, end); //avoid with clever management?
  my_inc--;
 } while (my_inc >= 1);
end_loop:
 return;
}
The following table shows the factorials std::next_permutation algorithm exhausted, the number used for skip_next_permutation and the time std::next_permutation used.


Factorial
Number
Time / ms
9!
362 879
0.9
12!
39 916 799
94.1
13!
6 227 020 799
14 436.6


The fast forward function skip_next_permutation uses approximately 0.009 ms for all the values.