Creating a DIY 433 MHz ESP8266-based Home Automation bridge to switch DIP remote control outlets

A couple of years ago I built a pretty basic smart home application allowing me to control my remote controlled sockets via an Android app or a Web Extension. It’s based on the rcswitch library run on an Apache. The 433 MHz signals are sent by a FS1000A transmitter hooked up via GPIO to a Raspberry Pi. The whole setup lied on the ground in a corner of my apartment behind a curtain next to my network wall jack. Now where we have just moved to a nice new and twice as big home, I needed a solution which could be placed in the middle of all rooms to allow the rather weak 433 MHz signals to reach every receiver. Additionally, I wanted to get rid of having to maintain a full Ubuntu server, which only serves as a light switch for the most part.

Firefox WebExtension used to turn on desk lamp
Firefox WebExtension used to turn on desk lamp

Around that time, I stumbled upon the ESP8266: a low-cost WiFi microchip with full TCP/IP stack and microcontroller capability – ideally soldered on a NodeMCU or Wemos D1 for the maximum level of convenience. Arduino and Wifi: a whole new world of IoT-possibilities. Once you’ve added the board manager to your Arduino IDE, you can use those tiny boards just like an ordinary Arduino. As the Arduino WebServer library can turn a NodeMCU development board into a light-weight HTTP server and the rcswitch library is also available on Arduino, I decided to put both – NodeMCU and FS1000A – into a junction box to create a DIY 433 MHz RF WiFi bridge.

To reach the bridge you should either assign a static IP or use mDNS. Keep in mind that mDNS is not supported by all operating systems out of the box. If in doubt, use a static IP.  To make the bridge accessible from outside your home network, you need to open and forward a port on your router (port 80 by default and can be changed in line 10). It’s recommend to secure any connection made through the public internet. By the time I was writing the script, there hasn’t been a HTTPS server implementation around. However, I found HelloServerBearSSL while writing this article. It looks very promising and is definitely worth a try.

433Mhz RF WiFi Bridge Junction Box opened and closed
433 MHz RF WiFi Bridge Junction Box opened and closed

This project works with simple DIP-switch remote outlets only. It became quite hard to get the “old” DIP outlets as most producers switched to the “new” outlets, which use a button on the receivers to “learn” a signal. There is a NewRemoteSwitch library to deal with them. But as I just recently found one last triple pack in a dollar store, I am stocked until I will eventually move to Wifi controlled outlets.

Enough talk. A typical request contains the five digit system code, five digit unit code and a binary power code separated by comma. You can also concatenate multiple commands using semicolon. A sample request to switch on outlet A and switch off outlet B would look like this:

x.x.x.x/switch?command=10010,00001,1;10010,0010,0

Here is the code. Further down is a download link.

#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#include <RCSwitch.h>
#include <WiFiClient.h>

const char* ssid = "SSID";
const char* password = "PASSWORD";

ESP8266WebServer server(80);
RCSwitch rcswitch = RCSwitch();


void setup(void) {

  Serial.begin(115200);

  digitalWrite(2, HIGH); // Turn onboard led off
  rcswitch.enableTransmit(15);

  WiFi.mode(WIFI_STA); //  (WIFI_AP) will spawn an access point. useful when no router is present.
  WiFi.begin(ssid, password);

  Serial.println("");

  if (WiFi.waitForConnectResult() != WL_CONNECTED) {

    Serial.println("Wifi connect failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }

  MDNS.begin("bridge"); // will make the bridge available under bridge.local

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  server.on("/switch", handleSwitchRequest); //Associate the handler function to the path

  server.begin();
  Serial.println("HTTP server started");
}


int countCharacterOccurrence(char *haystack, char needle) {

  int count = 0;

  for (int i = 0; i < strlen(haystack); i++)

    if (haystack[i] == needle) {

      count++;
    }

  return count;
}


void handleSwitchRequest() {

  bool isBadRequest = false;
  String response = "";

  if (server.arg("command") != "") {

    char commands[1024]; // make some room to get the string server args
    server.arg("command").toCharArray(commands, sizeof(commands)); // convert string to char array

    char *pointerCommands = commands; // create a pointer and store commands at its memory's address
    char *commandToken;
    char *subCommandToken; 

    while ((commandToken = strsep(&pointerCommands, ";")) != NULL) { // delimiter is the semicolon. we are using the address of the pointer to the input string. strsep expects the address (pointer) of a pointer to the string that should be separated. if found, pointerCommands is updated to point past the delimiter. returns a pointer to the result token.

      int counter = 0;
      char* commandArray[3];

      if (countCharacterOccurrence(commandToken, ',') != 2) {

        isBadRequest = true;
        response += "ERROR:\t" + String(commandToken) + " - Bad format. Expected: ?command=10000,10000,1;\n";
        continue;
      }

      while ((subCommandToken = strsep(&commandToken, ",")) != NULL) { // delimiter is the comma

        commandArray[counter++] = subCommandToken;
      }

      if (strcmp(commandArray[2], "1") == 0) {

        rcswitch.switchOn(commandArray[0], commandArray[1]);
        response += "ON:\t" + String(commandArray[0]) + "," + String(commandArray[1]) + "," + String(commandArray[2]) + "\n";

      } else if (strcmp(commandArray[2], "0") == 0) {

        rcswitch.switchOff(commandArray[0], commandArray[1]);
        response += "OFF:\t" + String(commandArray[0]) + "," + String(commandArray[1]) + "," + String(commandArray[2]) + "\n";

      } else {

        isBadRequest = true;
        response += "ERROR:\t" + String(commandArray[0]) + "," + String(commandArray[1]) + "," + String(commandArray[2]) + " - Power must be either 1 or 0.\n";
      }

      delay(50); // will otherwise hick up and fatal
    }
  }

  server.send(isBadRequest ? 400 : 200, "text / plain", response);
}


void loop(void) {

  server.handleClient();
}

DOWNLOAD: 433MHzWifiBridge Project

 

Generating random alphanumeric profanity free codes using pthreads in PHP

A friend of mine recently forwarded me an offer he received for the generation of 50 million random codes: alphanumeric, 10 characters long, unique and not containing any profane words. Price tag: same as a brand new mid-range car. Lol. Hold my beer, I’ll do this. 🙂

Not too long after writing the first line, the SQLite table filled with codes in a breeze…. until I cranked up the profanity check from a couple of test badwords to a real world scenario list of around 2500 finest German swear words. It absolutely killed the performance, while the CPU utilization did not even hit the 30% mark. Being to lazy to rewrite everything in e. g. Java, I started to take a look at ways to bring multi-threading to PHP. This led me to pthreads, a project providing multi-threading based on Posix Threads. Motivation follows action, action follows laziness and voilà: the code generator is now able to utilize all available processing power. Combined with a few tweaks of the bad word dictionary, it dramatically reduced the time needed to finish the job. A test run on my old i7 4something took two and a half hours (using this English profanity list and requiring a minimum Shannon entropy of theoretically 2.2 bits per character).

The whole project and its output can be downloaded below. Make sure to install pthreads first. The script configuration is done in the Config.php. Also note that pthreads projects can be run via CLI only.

A couple of learnings made:

* Use a multi-threading language in the first place when thinking about solving highly repetitive tasks.
* Use random_int() instead of rand(). Using rand() will quickly lead you to duplicate codes as it does not generate cryptographically secure values.
* Create objects, that need to be passed into a pthreads worker, in the calling context and keep a reference. Objects created in a thread scope constructor will be destroyed to avoid memory issues.
* Combining multiple SQL INSERTs to one transaction will take way less time than inserting one by one.
* Having an idea about the statistical probability of hitting a duplicate code or unwanted word, helps balancing out the efforts taken to avoid them. Keep in mind that every constraint will make it easier to guess a code.

DOWNLOAD: CodeGenerator Project
DOWNLOAD: 50 mio codes (1.1 GB, zipped)

Optimizing the Amazon rating histogram table

Amazon recently updated their website to show only the percentage of ratings behind each rating bar. While this usually comes in quite handy, it is counterproductive when there is only a small number of ratings. So I wrote a Greasemonkey script, which brings back the absolute number of ratings behind each bar and moves the percentage directly onto it. While working on this, I noticed that there is a very tiny little tooltip triangle right behind the average rating. It says that the shown average rating is not the arithmetic one, but instead a score that has been adjusted based on certain parameters (e. g. age, helpfulness, verified purchases) by Amazon. The first thought is obvious: “Sneaky Amazon. Nice trick to increase the ratings and drive sales.”. Turns out the shown average ratings are often lower than the arithmetic average. At least for the couple of samples I took. Could also be an approach to control sales by systematically devaluing certain products. Who knows… At least interesting enough to put it as a follow up project on the “maybe next winter” list. Still, there remains a bland taste and a strong feeling that Amazon ratings keep on becoming less and less trustworthy.

Note: Amazon keeps changing its markup quite often. The script might already have stopped working at the time you are reading this.

DOWNLOAD

 

Redirect multi-page news articles to a single page view using Greasemonkey

A lot of news websites split their articles into multiple pages. In theory this drives page impressions and thus ad revenue. In practice it is just annoying. The Greasemonkey script below automagically redirects you to a news article’s full page (in this case on zeit.de). It uses a MutiationObserver to wait for the pager UI element. The script kicks in once it appears – no need to wait until their page (or ads?!) have been fully loaded. 🙂

You can easily modify it:

1. Include all pages that might contain a multi-page article
2. Exclude all pages that might lead to a loop (especially the target site) or that will not contain a pager element (MutiationObserver can easily add a couple of milliseconds of loading time on complex pages)
3. Change the class name of the pager element to the one used by your site
4. Change the target location to the one you want to be redirect to

// ==UserScript==
// @name         Zeit Onepager
// @namespace    kubath.com
// @version      1.0
// @description  Zeigt mehrseitige Zeit-Artikel auf einer Seite
// @author       Florian Kubath
// @match        *://www.zeit.de/*
// @exclude      *://www.zeit.de/*/komplettansicht
// @grant        none
// @run-at       document-start
// ==/UserScript==

var mutationObserver;

window.addEventListener('load', function() {
  
  mutationObserver.disconnect();
}, false);

(function onepager() {

    mutationObserver = new MutationObserver(function (mutations) {

        var pager = document.getElementsByClassName('article-pager__all'); // Change to your site's pager element's class name
        if (pager.length > 0) {

            window.location.replace(window.location.href + '/komplettansicht'); // Change to your target site's URL 
            mutationObserver.disconnect();
            return;
        }
    });

    mutationObserver.observe(document, {

        childList: true,
        subtree: true
    });
}());

 

Ready-to-install scripts for:

golem.de
heise.de
zeit.de

The easiest way to send basic HTTP POST or GET requests using PHP

The easiest way to send basic HTTP POST or GET requests is using PHP’s built in file_get_contents() function in conjunction with HTTP context options:

$data = http_build_query(
    array(
        'firstKey' => 'firstValue',
        'secondKey' => 'secondValue',
        'thirdKey' => 'thirdValue'
    )
);

$options = array('http' =>
    array(
        'method'  => 'POST',
        'header'  => 'Content-type: application/x-www-form-urlencoded',
        'content' => $data
    )
);

$context = stream_context_create($options);

$result = file_get_contents('https://httpbin.org/post', false, $context);

Further reading:

file_get_contents
HTTP context options

Using a ML8511 UV sensor and an Arduino Nano to test UV-filtering properties of sunglasses

Do cheap freebie sunglasses really block UV light or are they mostly just toys, which pose a serious health risk to your eyes? That’s the question I asked myself when coming across one of my wife’s cheesy glasses. I always warned her, but never could proof the potential risk of increased UV exposure caused by non-blocking tinted glasses. Until now…

After buying one of those cheap Arduino Nano clones (oh the irony), I started experimenting with all kind of sensors. One of them was the ML8511. This sensor detects 280 – 390 nm light most effectively. This wavelength is categorized as part of the UVB spectrum and most of the UVA spectrum. Overexposure to UVA radiation has been linked to the development of certain types of cataracts, and research suggests UVA rays may play a role in development of macular degeneration.

The setup was straight forward: I hung an UV LED torch over the sensor using my helping hand. The emitted 395 nm light is slightly out of range, but the torch has proven to be a reliable source of detectable UV light. Pointing the beam directly at the photo resistor was crucial as the amount of measurable UV light decreases rapidly on the beam’s edges. I used a sketch from Sparkfun to read the sensor’s output. The unit measure wasn’t really necessary as I was mainly interested in the relative amount of absorbed UV light. But having the Milliwatts per square Centimeter came in handy. The problem was that the script outputted -1 mW/cm² when being in an UV-free environment. The solution was unexpected: The voltage of the “supposed to be 1% accurate” 3v3 Nano output was in fact only 10% accurate and came out as being 3.61 volts. Seems like testing cheap sunglasses using even cheaper tools isn’t the best idea. However, the sketch’s output can be calibrated by adjusting the hard-coded reference variable in the script to the actual reference voltage.

All tested sunglasses absorbed a fair amount of UV light. The best pairs filtered the UV light nearly completely (meaning below a level that can be detected by an ML8511 in this particular setup and ignoring the minor UV halo around the frame due to the sensor not being fully covered by the glass). The rest ranged between letting 1 – 10% of UV irradiation to pass through – proving that all sunglasses had UV-filtering properties. Given that a good UV protection can be bought for less than 10 Euros (the best pair tested), it is probably a good idea to not use freebie sunglasses if you have a bad gut feeling. As general advice, make sure that you buy your glasses from trusted retailers (optician, pharmacy, supermarket, etc.) or let them be tested.

Seeing is believing. 😉

 

UV test setup with ML8511 and Arduino Nano R3
Serial monitor output of the sketch used

 

 /* 
 ML8511 UV Sensor Read Example
 By: Nathan Seidle
 SparkFun Electronics
 Date: January 15th, 2014
 License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).

 The ML8511 UV Sensor outputs an analog signal in relation to the amount of UV light it detects.

 Connect the following ML8511 breakout board to Arduino:
 3.3V = 3.3V
 OUT = A0
 GND = GND
 EN = 3.3V
 3.3V = A1
 These last two connections are a little different. Connect the EN pin on the breakout to 3.3V on the breakout.
 This will enable the output. Also connect the 3.3V pin of the breakout to Arduino pin 1.

 This example uses a neat trick. Analog to digital conversions rely completely on VCC. We assume
 this is 5V but if the board is powered from USB this may be as high as 5.25V or as low as 4.75V:
 http://en.wikipedia.org/wiki/USB#Power Because of this unknown window it makes the ADC fairly inaccurate
 in most cases. To fix this, we use the very accurate onboard 3.3V reference (accurate within 1%). So by doing an
 ADC on the 3.3V pin (A1) and then comparing this against the reading from the sensor we can extrapolate
 a true-to-life reading no matter what VIN is (as long as it's above 3.4V).

 Test your sensor by shining daylight or a UV LED: https://www.sparkfun.com/products/8662

 This sensor detects 280-390nm light most effectively. This is categorized as part of the UVB (burning rays)
 spectrum and most of the UVA (tanning rays) spectrum.

 There's lots of good UV radiation reading out there:
 http://www.ccohs.ca/oshanswers/phys_agents/ultravioletradiation.html
 https://www.iuva.org/uv-faqs

*/

//Hardware pin definitions
int UVOUT = A0; //Output from the sensor
int REF_3V3 = A1; //3.3V power on the Arduino board

void setup()
{
  Serial.begin(9600);

  pinMode(UVOUT, INPUT);
  pinMode(REF_3V3, INPUT);

  Serial.println("ML8511 example");
}

void loop()
{
  int uvLevel = averageAnalogRead(UVOUT);
  int refLevel = averageAnalogRead(REF_3V3);

  //Use the 3.3V power pin as a reference to get a very accurate output value from sensor
  float outputVoltage = 3.3 / refLevel * uvLevel;   // adjust outputVoltage to actual voltage in case you read negative values.

  float uvIntensity = mapfloat(outputVoltage, 0.99, 2.8, 0.0, 15.0); //Convert the voltage to a UV intensity level

  Serial.print("output: ");
  Serial.print(refLevel);

  Serial.print("ML8511 output: ");
  Serial.print(uvLevel);

  Serial.print(" / ML8511 voltage: ");
  Serial.print(outputVoltage);

  Serial.print(" / UV Intensity (mW/cm^2): ");
  Serial.print(uvIntensity);

  Serial.println();

  delay(100);
}

//Takes an average of readings on a given pin
//Returns the average
int averageAnalogRead(int pinToRead)
{
  byte numberOfReadings = 8;
  unsigned int runningValue = 0; 

  for(int x = 0 ; x < numberOfReadings ; x++)
    runningValue += analogRead(pinToRead);
  runningValue /= numberOfReadings;

  return(runningValue);  
}

//The Arduino Map function but for floats
//From: http://forum.arduino.cc/index.php?topic=3922.0
float mapfloat(float x, float in_min, float in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;

}

 

Reference and further reading:

https://learn.sparkfun.com/tutorials/ml8511-uv-sensor-hookup-guide
https://en.wikipedia.org/wiki/Ultraviolet
http://www.canadianjournalofophthalmology.ca/article/S0008-4182(17)30495-7/fulltext

The straight forward way to backup all your phone’s files using the Android Debug Bridge

To copy all files from your Android phone’s internal storage to the current local dir using adb:

adb pull -p -a /sdcard/

(‘-p’ to display the transfer progress)
(‘-a’ means copy timestamp and mode)

 

The whole adb help for the sake of completeness:

Android Debug Bridge version 1.0.32
Revision eac51f2bb6a8-android

 -a                            - directs adb to listen on all interfaces for a connection
 -d                            - directs command to the only connected USB device
                                 returns an error if more than one USB device is present.
 -e                            - directs command to the only running emulator.
                                 returns an error if more than one emulator is running.
 -s <specific device>          - directs command to the device or emulator with the given
                                 serial number or qualifier. Overrides ANDROID_SERIAL
                                 environment variable.
 -p <product name or path>     - simple product name like 'sooner', or
                                 a relative/absolute path to a product
                                 out directory like 'out/target/product/sooner'.
                                 If -p is not specified, the ANDROID_PRODUCT_OUT
                                 environment variable is used, which must
                                 be an absolute path.
 -H                            - Name of adb server host (default: localhost)
 -P                            - Port of adb server (default: 5037)
 devices [-l]                  - list all connected devices
                                 ('-l' will also list device qualifiers)
 connect <host>[:<port>]       - connect to a device via TCP/IP
                                 Port 5555 is used by default if no port number is specified.
 disconnect [<host>[:<port>]]  - disconnect from a TCP/IP device.
                                 Port 5555 is used by default if no port number is specified.
                                 Using this command with no additional arguments
                                 will disconnect from all connected TCP/IP devices.

device commands:
  adb push [-p] <local> <remote>
                               - copy file/dir to device
                                 ('-p' to display the transfer progress)
  adb pull [-p] [-a] <remote> [<local>]
                               - copy file/dir from device
                                 ('-p' to display the transfer progress)
                                 ('-a' means copy timestamp and mode)
  adb sync [ <directory> ]     - copy host->device only if changed
                                 (-l means list but don't copy)
                                 (see 'adb help all')
  adb shell                    - run remote shell interactively
  adb shell <command>          - run remote shell command
  adb emu <command>            - run emulator console command
  adb logcat [ <filter-spec> ] - View device log
  adb forward --list           - list all forward socket connections.
                                 the format is a list of lines with the following format:
                                    <serial> " " <local> " " <remote> "\n"
  adb forward <local> <remote> - forward socket connections
                                 forward specs are one of: 
                                   tcp:<port>
                                   localabstract:<unix domain socket name>
                                   localreserved:<unix domain socket name>
                                   localfilesystem:<unix domain socket name>
                                   dev:<character device name>
                                   jdwp:<process pid> (remote only)
  adb forward --no-rebind <local> <remote>
                               - same as 'adb forward <local> <remote>' but fails
                                 if <local> is already forwarded
  adb forward --remove <local> - remove a specific forward socket connection
  adb forward --remove-all     - remove all forward socket connections
  adb reverse --list           - list all reverse socket connections from device
  adb reverse <remote> <local> - reverse socket connections
                                 reverse specs are one of:
                                   tcp:<port>
                                   localabstract:<unix domain socket name>
                                   localreserved:<unix domain socket name>
                                   localfilesystem:<unix domain socket name>
  adb reverse --norebind <remote> <local>
                               - same as 'adb reverse <remote> <local>' but fails
                                 if <remote> is already reversed.
  adb reverse --remove <remote>
                               - remove a specific reversed socket connection
  adb reverse --remove-all     - remove all reversed socket connections from device
  adb jdwp                     - list PIDs of processes hosting a JDWP transport
  adb install [-lrtsdg] <file>
                               - push this package file to the device and install it
                                 (-l: forward lock application)
                                 (-r: replace existing application)
                                 (-t: allow test packages)
                                 (-s: install application on sdcard)
                                 (-d: allow version code downgrade)
                                 (-g: grant all runtime permissions)
  adb install-multiple [-lrtsdpg] <file...>
                               - push this package file to the device and install it
                                 (-l: forward lock application)
                                 (-r: replace existing application)
                                 (-t: allow test packages)
                                 (-s: install application on sdcard)
                                 (-d: allow version code downgrade)
                                 (-p: partial application install)
                                 (-g: grant all runtime permissions)
  adb uninstall [-k] <package> - remove this app package from the device
                                 ('-k' means keep the data and cache directories)
  adb bugreport                - return all information from the device
                                 that should be included in a bug report.

  adb backup [-f <file>] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared] [-all] [-system|-nosystem] [<packages...>]
                               - write an archive of the device's data to <file>.
                                 If no -f option is supplied then the data is written
                                 to "backup.ab" in the current directory.
                                 (-apk|-noapk enable/disable backup of the .apks themselves
                                    in the archive; the default is noapk.)
                                 (-obb|-noobb enable/disable backup of any installed apk expansion
                                    (aka .obb) files associated with each application; the default
                                    is noobb.)
                                 (-shared|-noshared enable/disable backup of the device's
                                    shared storage / SD card contents; the default is noshared.)
                                 (-all means to back up all installed applications)
                                 (-system|-nosystem toggles whether -all automatically includes
                                    system applications; the default is to include system apps)
                                 (<packages...> is the list of applications to be backed up.  If
                                    the -all or -shared flags are passed, then the package
                                    list is optional.  Applications explicitly given on the
                                    command line will be included even if -nosystem would
                                    ordinarily cause them to be omitted.)

  adb restore <file>           - restore device contents from the <file> backup archive

  adb disable-verity           - disable dm-verity checking on USERDEBUG builds
  adb enable-verity            - re-enable dm-verity checking on USERDEBUG builds
  adb keygen <file>            - generate adb public/private key. The private key is stored in <file>,
                                 and the public key is stored in <file>.pub. Any existing files
                                 are overwritten.
  adb help                     - show this help message
  adb version                  - show version num

scripting:
  adb wait-for-device          - block until device is online
  adb start-server             - ensure that there is a server running
  adb kill-server              - kill the server if it is running
  adb get-state                - prints: offline | bootloader | device
  adb get-serialno             - prints: <serial-number>
  adb get-devpath              - prints: <device-path>
  adb remount                  - remounts the /system, /vendor (if present) and /oem (if present) partitions on the device read-write
  adb reboot [bootloader|recovery]
                               - reboots the device, optionally into the bootloader or recovery program.
  adb reboot sideload          - reboots the device into the sideload mode in recovery program (adb root required).
  adb reboot sideload-auto-reboot
                               - reboots into the sideload mode, then reboots automatically after the sideload regardless of the result.
  adb reboot-bootloader        - reboots the device into the bootloader
  adb root                     - restarts the adbd daemon with root permissions
  adb unroot                   - restarts the adbd daemon without root permissions
  adb usb                      - restarts the adbd daemon listening on USB
  adb tcpip <port>             - restarts the adbd daemon listening on TCP on the specified port
networking:
  adb ppp <tty> [parameters]   - Run PPP over USB.
 Note: you should not automatically start a PPP connection.
 <tty> refers to the tty for PPP stream. Eg. dev:/dev/omap_csmi_tty1
 [parameters] - Eg. defaultroute debug dump local notty usepeerdns

adb sync notes: adb sync [ <directory> ]
  <localdir> can be interpreted in several ways:

  - If <directory> is not specified, /system, /vendor (if present), /oem (if present) and /data partitions will be updated.

  - If it is "system", "vendor", "oem" or "data", only the corresponding partition
    is updated.

environmental variables:
  ADB_TRACE                    - Print debug information. A comma separated list of the following values
                                 1 or all, adb, sockets, packets, rwx, usb, sync, sysdeps, transport, jdwp
  ANDROID_SERIAL               - The serial number to connect to. -s takes priority over this if given.
  ANDROID_LOG_TAGS             - When used with the logcat option, only these debug tags are printed.

 

 

Stepscout: A tool to search for jobs and apartments at the same time

I think living near one’s workplace is a great benefit for a good work-live balance. If you are living in your current city for a while, you probably already know where the best places to live and work are. But if you are starting to search for one or the other, you might ask yourself which neighborhood is the best to reduce the daily commute to a minimum. To find answers to this question I build Stepscout. It leverages the Stepstone and ImmobilienScout24 API’s to find jobs & apartments in one go and marks them together on a Google Map. This visualization helps to find job clusters, apartment clusters or ideally job-apartment-clusters.

The trick in this project was to make use of Google’s Places API to search for latitude and longitude of every company in the Stepstone result response. Even though Stepstone’s response JSON contains fields for geographic coordinates, they are empty or filled with generic values for the most part.

If you are living in Germany, you can check out the tool here or get a first impression below.

Boilerplate for a basic PHP cURL POST or GET request with parameters on Apache

cURL is a library for transferring data using various protocols – in this case most importantly HTTP POST and GET. PHP installed on a Linux distribution or as part of XAMPP uses libcurl. If you haven’t enabled cURL yet, open your php.ini and remove the semicolon at the beginning of this line:

;extension=php_curl.dll
^--- remove this semicolon

You will find the location of your php.ini in the output’s first line when running

php --ini

on the command line or by using the XAMPP control panel on a Windows machine. Click the ‘Config’ button next to the Apache module and select ‘PHP (php.ini)’ from the context menu. Save the changes and restart Apache – either by pressing ‘Stop’ & ‘Start’ on the XAMPP control panel or by using the Linux command line:

sudo service apache2 restart

If cURL for PHP isn’t installed, run

sudo apt-get install php-curl

 prior to the step above.

You’ll find further information on how to use cURL here: http://php.net/manual/en/book.curl.php

This boilerplate wraps cURL in a simple function with four parameters: request type, url, parameters and headers. The first snippet contains comments for every step. The second snippet is exactly the same code but without any comments.

Commented boilerplate:

<?php
 
$url = 'http://httpbin.org/post';
 
$parameters = array(
      'firstKey' => 'firstValue',
      'secondKey' => 'secondValue',
      'thirdKey' => 'thirdValue'
);
 
$headers = array(
      'X-Custom: 123',
      'X-Api-Key: ABC123'
);
 
// fire the request. Access the response object with $response->success and $response->body
$response = request('POST', $url, $parameters, $headers);

     
/**
 * cURL a resource with parameters
 *
 * @param string $requestType The request type POST or GET  
 * @param string $url The request URL
 * @param array|null $parameters An array of request parameters
 * 
 * @return object Response object with $success (bool) and $body (string) property.
 */ 
function request($requestType, $url, $parameters = null, $headers = null){
 
      // instantiate the response object
      $response = new stdClass();

      // check if cURL is enabled
      if(!function_exists('curl_init')){

            $response->success = false;
            $response->body = 'cURL is not enabled.';

            return $response;
      }
 
      // instantiate a cURL instance and set the handle
      $ch = curl_init();
 
      // build http query if $parameters is not null. Parameters with null as value will be removed from query.
      ($parameters !== null) ? $query = http_build_query($parameters) : $query = '';
 
      // POST:
      if($requestType === 'POST'){
 
            // 1 tells libcurl to do a regular HTTP post and sets a "Content-Type: application/www-form-urlencoded" header by default
            curl_setopt($ch,CURLOPT_POST, 1);                
            // add the query as POST body
            curl_setopt($ch,CURLOPT_POSTFIELDS, $query);     
            
      // GET:
      }elseif ($requestType === 'GET') {
      
            // if not empty, add parameters to URL
            if($query) $url = $url . '?' . $query;                
               
      // ELSE: 
      }else{
 
            $response->success = false;
            $response->body = 'request type GET or POST is missing.';

            return $response;
      }
      
      // set the URL
      curl_setopt($ch, CURLOPT_URL, $url);                  
      // tell cURL to return the response body. A successful request will return true if not set.
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);       
      // disable ssl certificate checks. Dirty, insecure workaround for common error "SSL Error: unable to get local issuer certificate". Fix it the correct way and remove the line!
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);      
      // more options here: http://php.net/manual/en/function.curl-setopt.php
 
      // add headers if present
      if ($headers !== null) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

      // execute and store the result
      $result = curl_exec($ch);                             
 
      // check if request was successful. If yes, return result. If not, return error and its code.
      if($result !== false){

            $response->success = true;
            $response->body = $result;

      }else{

            $response->success = false;
            $response->body = curl_error($ch);
            $response->error = curl_errno($ch);
      }                  
      
      // close session and delete handle
      curl_close($ch);                                      

      // return response object
      return $response;                                       
}
 
 
 
?>

 

And the raw template without comments:

<?php
 
$url = 'http://httpbin.org/post';
 
$parameters = array(
      'firstKey' => 'firstValue',
      'secondKey' => 'secondValue',
      'thirdKey' => 'thirdValue'
);
 
$headers = array(
      'X-Custom: 123',
      'X-Api-Key: ABC123'
);
 

$response = request('POST', $url, $parameters, $headers);


function request($requestType, $url, $parameters = null, $headers = null){
 
      $response = new stdClass();

      if(!function_exists('curl_init')){

            $response->success = false;
            $response->body = 'cURL is not enabled.';

            return $response;
      }
 
      $ch = curl_init();
 
      ($parameters !== null) ? $query = http_build_query($parameters) : $query = '';
 
      if($requestType === 'POST'){
 
            curl_setopt($ch,CURLOPT_POST, 1);                
            curl_setopt($ch,CURLOPT_POSTFIELDS, $query);  

      }elseif ($requestType === 'GET') {

            if($query) $url = $url . '?' . $query;

      }else{
 
            $response->success = false;
            $response->body = 'request type GET or POST is missing.';

            return $response;
      }
      
      curl_setopt($ch, CURLOPT_URL, $url);                  
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);       
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);      
 
      if ($headers !== null) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

      $result = curl_exec($ch);                             

      if($result !== false){

            $response->success = true;
            $response->body = $result;

      }else{

            $response->success = false;
            $response->body = curl_error($ch);
            $response->error = curl_errno($ch);
      }                  
      
      curl_close($ch);                                      

      return $response;                                       
}
 
 
 
?>

 

 

Disable autoplay on Youtube’s new 2017 material design release

I happened to receive Youtube’s new 2017 desktop material design when I was watching videos the other day. All in all a great redesign which closes the gap to their other channels. You can force the new design by running the command below in your Firefox’s console’s command line (press [CMD] + [SHIFT] + [K]). Reload the page when done.

document.cookie="PREF=f6=4;path=/;domain=.youtube.com";

Unfortunately, the Firefox Greasemonkey script I used to disable Youtube’s autoplay feature does not work with their new site. Just removing the autoplay toggle’s node from the DOM does not do the trick anymore. My investigations brought to light that the f5 property of the PREF cookie is used to toggle the autoplay feature under the hood. f5=30000 is the default value to disable autoplay and f5=20000 the default to enable it. So I built a new Greasemonkey script which reads the existing PREF cookie, looks for the f5-property and sets it accordingly (or adds it if not present). At the same time all existing values are preserved. Additionally, the autonav_disable cookie is set. It was the first thing I found during my investigations which made me think “easy….”. Anyways, it turned out this cookie is not used to control autoplay. Not sure what it does, but I set it just to play safe. Finally a MutationObserver is used to wait for the autoplay toggle and remove it once it is loaded. DOMContentLoaded did not help as it seems like the node is added afterwards. To install the script, first get Greasemonkey for Firefox here . Once Greasemonkey is installed, click here to install the Userscript or paste the code below into your own script. Force reload Youtube by pressing [CMD] + [F5] after successfully installing the script.

// ==UserScript==
// @name        Disable Youtube Autoplay Material Design
// @namespace   https://kubath.com
// @include     *youtube.com/*
// @version     1
// @grant       none
// @run-at      document-start
// ==/UserScript==

/*
 * This script disables Youtube's autoplay feature on the new 2017 material design page. 
 * The new design is currently in test and will not be shown to every user. 
 * To enforce Youtube's new material design run the command below in the console and reload the page.
 * document.cookie="PREF=f6=4;path=/;domain=.youtube.com";
 * 
 * This script will also disable autoplay on Youtube's previous page (prior to 2017).
 *
 * Bonus: To hide the cookie consent header uncomment setCookieConsentHideCookie(); below.
*/


var cookieDomainValue = '.youtube.com';
var prefCookieKey = 'PREF';
var prefCookieAutoplayToggleKey = 'f5';
var prefCookieAutoplayToggleValueAutonavDisabled = 30000;


setAutonavDisabledCookie(); // set autonav_disable cookie
setAutonavDisabledPrefCookie(); // set autoplay pref cookie (f5 = 30000)
// setCookieConsentHideCookie(); // uncomment to hide cookie consent bar
removeAutonavElement(); // remove autoplay element


// get cookies
function getCookie(name) {
  var value = '; ' + document.cookie;
  var parts = value.split('; ' + name + '=');
  if (parts.length == 2) return parts.pop().split(';').shift();
}


// wait for the autoplay bar to appear and remove it
function removeAutonavElement() {
  var mutationObserver = new MutationObserver(function (mutations, mutationObserverInstance) {
    var element = document.getElementById('head');
   
    if (element) {
      element.remove();
      mutationObserverInstance.disconnect();
      return;
    }
  });
  
  mutationObserver.observe(document, {
    childList: true,
    subtree: true
  });
}


// set the autonav_disabled cookie (which appears sometimes but does not seem to have any effect)
function setAutonavDisabledCookie() {
 
  document.cookie = 'autonav_disabled=true; path=/; domain=' + cookieDomainValue;
}


// get the PREF cookie, search for the f5 key and set the required value to disable Youtube autoplay
function setAutonavDisabledPrefCookie() {
 
  var input = getCookie(prefCookieKey);
  var output = '';
  
  if (input && input.indexOf('=') !== -1) {
    var inputArray = input.split('&');
    var outputArray = {};

    var found = false;
    
    for (var i = 0; i < inputArray.length; i++) {
      
      var temp = inputArray[i].split('=');
      
      if(!temp[1]){
        
        temp[1] = '';
      }
      
      outputArray[temp[0]] = temp[1];
    }
    
    for (var key in outputArray) {
      
      if (key == prefCookieAutoplayToggleKey) {
        
        found = true;
        outputArray[key] = prefCookieAutoplayToggleValueAutonavDisabled;
      }
      
      if (output == '') {
        
        output = key + '=' + outputArray[key];
      } else {
        
        output = output + '&' + key + '=' + outputArray[key];
      }
    }
  }
  
  if (!found) {
    
    if(output == ''){
      
      output = 'f5=30000';
    }else{
      
      output = output + '&f5=30000';
    }
  }
  
  document.cookie = prefCookieKey + '=' + output + ';  path=/; domain=' + cookieDomainValue;
}


// bonus: set the "remind me later" cookie for the cookie consent bar
function setCookieConsentHideCookie(){
  
  document.cookie = 'HideTicker=true; path=/; domain=' + cookieDomainValue;
}

Bonus: To hide Youtube’s cookie consent bar, uncomment the first occurrence of setCookieConsentHideCookie(); in the script.