December 2, 2016

My Custom DIY 60% Keyboard

My Custom DIY 60% Keyboard

All the parts


1x PCB Stabilizer set (6.25 space) - $6
1x Royal Oak Glam 60% case (Black Walnut) - $78
Solder, Tools, Risers, Labor, etc.

Total: ± $300

(Other single switch types added as needed)

Testing the board

I had my helper do the work here. We needed to test the board before applying any solder.


Add the stabilizers first

Add the plate (using a few switches as spacers)

Then add the rest

Solder solder solder...

And test again:

The casing

The casing I received did to have built-in risers:

So I added my own:

Then Assemble (using temporary spare DSA Carbon Cherry profile keycaps):

Hmm... pretty.

Programming the GH60

The board is controlled by a ATmega32U4 microcontroller (version 1/2/3) and is programmed using a DFU bootloader. I needed to install libusb and use Easy AVR keyboard mapper, then configured my layout, functions, macros and layers:

November 22, 2016

ABAP Bugs and peculiarities

Some of the design decisions in the ABAP language are a leap backwards when the intention was to leap at best sideways to thrust us into the meaningless paradigm of the now-defunct world of 4GL languages.

ABAP is a horrible language on all counts. Syntax-wise and how it is implemented, but here are just some of the succinct little tidbits I have found and ways to try and circumvent them.

1. Divide by zero is OK! sometimes...

So X / 0 in my opinion is never OK. When X != 0, the result is +-Infinity and for X = 0 the result is undefined. Both should result in an error. Well, for some reason ABAP decided 0 / 0 = 0. Without blinking an eyelid. Well done.

To fix this, and create an exception in all cases of X and Y in expression X / Y, re-write it as:

X / Y -> ( X * ( 1 / Y ) )

The same problem happens with modulus, however the solution is not as elegant:

X mod Y -> ( ( 1 mod Y ) * 0 + X / Y ) )

2. float parsing on older system fail when assigned to a non-F variable!

On pre-702 systems (where there are no DECFLOATS either) the following results in an error:

l_packed = '1e-1'.

The problem is that it expects a simple decimal numeric representation, but instead finds a character 'e' in the text. The solution is to assign it to a simple temporary float variable, and then assign it to the packed. For this purpose one can just create a global static float someplace since it is very temporary (unless two floats need to be parsed at the same time. However this can be slow and cumbersome, and then also has the problem of working with multiple values in a single expression.

A better solution is to also use a R/O global float and assign it the value of 1. And then to rewrite the literal float values as:

l_packed = ( cl_global_class=>s_float_unity * '1e-1' ).

3. Unary negation and implied whitespace-imposed precedence.

Take care, in ABAP:

-1 * 3 ** 2 = -9 , and
-3 ** 2 = 9 , BUT

- 3 ** 2 = -9

Almost (but not quite) as bad an idea as spacing in Python that changes meaning. Wow.

4. Problems with CHANGING any via a char1

On older unpatched systems the following program dumps:
REPORT zerror_test.
    METHODS method_true RETURNING value(r_val) TYPE flag.
    METHODS execute CHANGING c_result TYPE any.
  METHOD method_true.
    " This only dumps with returning C1
 (int + strings are OK) 
    " Thus it could be a unicode/utf-8 underlying string issue?
    r_val = 'X'.
  METHOD execute.
    " Assigning here to 'X' instead works in all cases:
    c_result = method_true( ).
DATA: lcl_test TYPE REF TO cl_test.

" this works:
DATA: lr_result TYPE REF TO data.
FIELD-SYMBOLS: <l_result> TYPE any.
CREATE DATA lr_result TYPE string.
ASSIGN lr_result->* TO <l_result>.
lcl_test->execute( CHANGING c_result = <l_result> ).
WRITE :/ <l_result>.

" this works as well
DATA: l_result TYPE string.
l_result = lcl_test->method_true( ).
WRITE :/ l_result.

" this does not work:
ASSIGN l_result TO <l_result>.
lcl_test->execute( CHANGING c_result = <l_result> ).
WRITE :/ <l_result>.

" this does not work:
lcl_test->execute( CHANGING c_result = l_result ).
WRITE :/ l_result.

In summary:

1) and 3) above are fundamental flaws and will not and cannot be fixed lest almost all applications start dumping and/or give different results. But 2) and 4) can (and has) been fixed in newer releases.

November 5, 2015

Retina Macbook Pro Bluetooth woes [partial fix]

Ever since I bought a pair of Bluetooth 4 headphones (with AptX) I've been noticing sporadic issues connecting with my Retina Macbook Pro. I've also seen posts of countless other people having the same issues. Needless to say an Apple Genius was unable to help me, but I was eventually able to get a solution thanks to the internets!

My problem being, that after a while (or after being put to sleep), my Bluetooth would not reconnect to my headphones, nor would it even see any other devices nearby. I've tried repairing and switching Bluetooth off and on again, but nothing helps. The only thing that helps is to reboot my Mac. This is kind of troublesome since I may be busy with a VMWare session debugging code in a session etc. This problem has persisted since Mavericks up to El Capitan so far.

Anyway, a solution someone proposed was to switch the WiFi off and back on again... and this solved my issue. For me this is cool, since VMWare seems to keep my network stable enough that my connections in my VM do not drop. Woo!

It kinda makes sense because they both probably use the same chip, but I would have expected switching Bluetooth off and on would have worked as well. Strange.

In case anyone else has the same issue, I hope this at least helps them.

September 17, 2015

How to OTA update your rooted Nexus. (quickly unroot)

Quite a few posts out there seem to think the only way to accomplish this is to do a factory reset and/or wipe all user data. As Android is moving to a faster release cycle, combined with the new measures to ensure an unaltered installation, this procedure is going to become more used. It might also be possible to flash the latest factory images non-destructively to emulate a OTA, but I'm not 100% sure of this (yet).

That is not true, all you will need to do is to revert certain partitions on your phone to stock (boot, recovery and system), and you can do this without wiping apps or user data.

This is because of new restrictions in Android Lollipop (for whatever reason)

I had to flash a custom boot when 5.0 was released to be able to root my Nexus 5 using SuperSU. And this combined with the modifications this did to my system partition prevented the OTA update from working, resulting in an 'Error!' message during update.

All you need to do is to download the original post-5 factory image that your current device is using (check this before booting into fastboot mode), then reboot into fastboot (Volume-Down + Power) then:

First extract archive:
~> tar -xzf hammerhead-XXXXXX-factory-XXXXXXX.tgz

1) If you modified your bootloader, reflash bootloader: (you can root without doing this, which is what I prefer)
~> fastboot flash bootloader bootloader-hammerhead-XXXXX.img

2) If you rooted:

In the extracted folder, extract the various images:
~> unzip

Then flash all the non-userdata image partitions with stock: (only the system is really needed for newer root methods)
~> fastboot flash system system.img

~> fastboot flash recovery recovery.img
~> fastboot flash boot boot.img

Anyway, this worked for me. After the OTA you can then again root your phone as required:

September 3, 2015

Garage door opener using Arduino + SmartThings

I wired up and controlled my garage (two main doors controlling and one side door sensor only) with the following customizable source code running on an Arduino and the SmartThings platform (via a Z Wave shield. (The .groovy file you load on the web, and the .ino file you compile for the Arduino)

These are the materials I used:

$35 Smarthings Arduino Z Wave shield
$25 Arduino Uno
$10 Power supply for Arduino
$10 Wire
$20 Magnetic switches
$8   Relay

It was pretty easy, I developed all the source and did the installation in a single day (tweaked it a little the rest of the weekend) and it has been running stable for about 1.5 years now.

The garage doors are toggles that I wired into my garage controller button wires, and they only show if the garage doors are ajar. (It does not indicate whether they are closing or opening... that was troublesome to detect with just a magnetic switch and the way garage doors open and close.)

No express or implied warranty are attached to this guide... your milage may vary, etc.

Here is how it looks on my SmartThings (Android) app:

* Arduino Garage * * Author: Marius Piedallu van Wyk * Date: 2016-08-17 */ metadata { // Automatically generated. Make future change here. definition (name: "Arduino Garage", namespace: "Lailoken", author: "Marius Piedallu van Wyk") { capability "Switch" // capability "Sensor" capability "Contact Sensor" attribute "contact", "string" // A virtual contact to indicate if anything is open attribute "leftDoor", "string" // left door open attribute "rightDoor", "string" // right door open attribute "backDoor", "string" // side walk-in door open command "pushLeft" // toggle left command "pushRight" // toggle right } simulator { } // Preferences // tile definitions tiles { standardTile("contact", "", width: 1, height: 1, canChangeIcon: true, canChangeBackground: true) { state "closed", label: 'Closed', icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821" state "open", label: 'Open', icon: "st.doors.garage.garage-open", backgroundColor: "#ffa81e" } standardTile("leftDoor", "device.leftDoor", width: 1, height: 1, canChangeIcon: true, canChangeBackground: true) { state "closed", label: "Closed", icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821", action: "pushLeft", nextState: "open" state "open", label: "Open", icon: "st.doors.garage.garage-open", backgroundColor: "#ffa81e", action: "pushLeft", nextState: "toggle" state "toggle", label: "Toggle", icon: "st.doors.garage.garage-opening", backgroundColor: "#89C2E8", action: "pushLeft", nextState: "toggle" } standardTile("rightDoor", "device.rightDoor", width: 1, height: 1, canChangeIcon: true, canChangeBackground: true) { state "closed", label: "Closed", icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821", action: "pushRight", nextState: "open" state "open", label: "Open", icon: "st.doors.garage.garage-open", backgroundColor: "#ffa81e", action: "pushRight", nextState: "toggle" state "toggle", label: "Toggle", icon: "st.doors.garage.garage-opening", backgroundColor: "#89C2E8", action: "pushRight", nextState: "toggle" } standardTile("backDoor", "device.backDoor", width: 1, height: 1, canChangeIcon: true, canChangeBackground: true) { state "closed", label: "Closed", icon: "", backgroundColor: "#79b821" state "open", label: "Open", icon: "", backgroundColor: "#ffa81e" } main(["contact"]) details(["leftDoor", "rightDoor", "backDoor"]) } preferences { } } def parse(String description) { def msg = zigbee.parse(description)?.text log.debug "Parse got '${msg}'" def parts = msg.split(" ") def name = parts.length>0?parts[0].trim():null def value = parts.length>1?parts[1].trim():null name = value != "ping" ? name : null def result if(name == "anyDoor") { // Use anyDoor as the virtual contact sensor for whole space: result = createEvent(name: "contact", value: value) } else { result = createEvent(name: name, value: value) } log.debug result return result} def pushLeft() { log.debug "Left Button pressed" zigbee.smartShield(text: "pushLeft").format()} def pushRight() { log.debug "Right Button pressed" zigbee.smartShield(text: "pushRight").format()}


* Arduino Garage * * Author: Marius Piedallu van Wyk * Date: 2014-07-27 */ //***************************************************************************** #include <SoftwareSerial.h> #include <SmartThings.h> #define DO_BACK_DOOR //***************************************************************************** // Pin Definitions //***************************************************************************** #define PIN_LED 13 #define PIN_THING_RX 3 #define PIN_THING_TX 2 #define PIN_BACK_DOOR_CONTACT 4 // input #define PIN_RIGHT 6 // out #define PIN_RIGHT_CONTACT 7 // input #define PIN_LEFT_CONTACT 8 // input #define PIN_LEFT 9 // out #define OPEN 0 // HIGH #define CLOSED 1 // LOW #define UNKNOWN 2 // --- reset / force update #define PUSH_DELAY 1200 // milliseconds to keep the button "pushed" //***************************************************************************** // Global Variables //***************************************************************************** SmartThingsCallout_t messageCallout; // call out function forward decalaration SmartThings smartthing(PIN_THING_RX, PIN_THING_TX, messageCallout); // constructor int leftClosed = UNKNOWN; int rightClosed = UNKNOWN; int backClosed = UNKNOWN; int anyOpen = UNKNOWN; bool isDebugEnabled=false; // enable or disable debug in this example int stateLED; // state to track last set value of LED int stateNetwork; // state of the network //***************************************************************************** // Local Functions //***************************************************************************** void pushLeft() { smartthing.shieldSetLED(0, 0, 2); // blue digitalWrite(PIN_LEFT,LOW); delay(PUSH_DELAY); digitalWrite(PIN_LEFT,HIGH); smartthing.shieldSetLED(0, 0, 0); // off } //***************************************************************************** void pushRight() { smartthing.shieldSetLED(0, 2, 0); // green digitalWrite(PIN_RIGHT,LOW); delay(PUSH_DELAY); digitalWrite(PIN_RIGHT,HIGH); smartthing.shieldSetLED(0, 0, 0); // off } int isClosed(int pin) { // LOW -> closed // HIGH -> open return (digitalRead(pin) == LOW)?CLOSED:OPEN; } void updateDoorState() { int newState; char* msg = NULL; newState = isClosed(PIN_LEFT_CONTACT); if (leftClosed != newState) { leftClosed = newState; if(leftClosed == CLOSED) msg = "leftDoor closed"; else msg = "leftDoor open"; smartthing.send(msg); if(isDebugEnabled) Serial.println(msg); return; // only one message per loop } newState = isClosed(PIN_RIGHT_CONTACT); if (rightClosed != newState) { rightClosed = newState; if(rightClosed == CLOSED) msg = "rightDoor closed"; else msg = "rightDoor open"; smartthing.send(msg); if(isDebugEnabled) Serial.println(msg); return; // only one message per loop } #ifdef DO_BACK_DOOR newState = isClosed(PIN_BACK_DOOR_CONTACT); if (backClosed != newState) { backClosed = newState; if(backClosed == CLOSED) msg = "backDoor closed"; else msg = "backDoor open"; smartthing.send(msg); if(isDebugEnabled) Serial.println(msg); return; // only one message per loop } #endif newState = CLOSED; #ifdef DO_BACK_DOOR if(rightClosed == OPEN || leftClosed == OPEN || backClosed == OPEN) newState = OPEN; #else if(rightClosed == OPEN || leftClosed == OPEN) newState = OPEN; #endif if(anyOpen != newState) { anyOpen = newState; if(anyOpen == CLOSED) msg = "anyDoor closed"; else msg = "anyDoor open"; smartthing.send(msg); if(isDebugEnabled) Serial.println(msg); return; // only one message per loop } } //***************************************************************************** void setNetworkStateLED() { SmartThingsNetworkState_t tempState = smartthing.shieldGetLastNetworkState(); if (tempState != stateNetwork) { switch (tempState) { case STATE_NO_NETWORK: if (isDebugEnabled) Serial.println("NO_NETWORK"); smartthing.shieldSetLED(2, 2, 0); // red break; case STATE_JOINING: if (isDebugEnabled) Serial.println("JOINING"); smartthing.shieldSetLED(2, 2, 0); // yellow break; case STATE_JOINED: if (isDebugEnabled) Serial.println("JOINED"); smartthing.shieldSetLED(0, 0, 0); // off // force report of current door state leftClosed = UNKNOWN; rightClosed = UNKNOWN; backClosed = UNKNOWN; anyOpen = UNKNOWN; break; case STATE_JOINED_NOPARENT: if (isDebugEnabled) Serial.println("JOINED_NOPARENT"); smartthing.shieldSetLED(2, 0, 2); // purple break; case STATE_LEAVING: if (isDebugEnabled) Serial.println("LEAVING"); smartthing.shieldSetLED(2, 2, 0); // yellow break; default: case STATE_UNKNOWN: if (isDebugEnabled) Serial.println("UNKNOWN"); smartthing.shieldSetLED(2, 0, 0); // red break; } stateNetwork = tempState; } } //***************************************************************************** // API Functions | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | // V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V //***************************************************************************** void setup() { // setup default state of global variables isDebugEnabled = true; stateLED = 0; // matches state of hardware pin set below stateNetwork = STATE_JOINED; // set to joined to keep state off if off // setup hardware pins pinMode(PIN_LED, OUTPUT); // define PIN_LED as an output pinMode(PIN_RIGHT, OUTPUT); pinMode(PIN_LEFT, OUTPUT); digitalWrite(PIN_RIGHT, HIGH); digitalWrite(PIN_LEFT, HIGH); digitalWrite(PIN_LED, LOW); // set value to LOW (off) to match stateLED=0 pinMode(PIN_LEFT_CONTACT, INPUT_PULLUP); pinMode(PIN_RIGHT_CONTACT, INPUT_PULLUP); pinMode(PIN_BACK_DOOR_CONTACT, INPUT_PULLUP); if(isDebugEnabled) { // setup debug serial port Serial.begin(9600); // setup serial with a baud rate of 9600 Serial.println("setup.."); // print out 'setup..' on start } } //***************************************************************************** void loop() { // run smartthing logic; // Check network connections (and send initial states on Join) setNetworkStateLED(); if(stateNetwork == STATE_JOINED) { // Check the open/closed state of the doors updateDoorState(); } } //***************************************************************************** void messageCallout(String message) { smartthing.shieldSetLED(2, 2, 2); // white // if debug is enabled print out the received message if(isDebugEnabled) { Serial.print("Received message: '"); Serial.print(message); Serial.println("' "); } if(message.equals("pushLeft")) { pushLeft(); } else if(message.equals("pushRight")) { pushRight(); } smartthing.shieldSetLED(0, 0, 0); // off }