C++ cross-compilation on Linux for the FreeBSD target using GCC or CLANG


Sometime it could be convenient to generate FreeBSD binaries on the Linux host. Just a few examples:

  • CI/CD deployment with a compilation check on the Linux Containers. It is especially the case for the SaaS environments where you are not able to select another OS (or it is not supported by the vendor)
  • Ability to automatically generate binaries for the all supported platforms with a minimal cost/efforts.
  • Using single docker or other linux-specific technology to cover all platforms.

I am using osxcross without any problem to generate OSX binaries, so decided to do the same with FreeBSD. This article shows how to do this and will provide some automated examples.

Using GCC

Most of the articles in the net i found are recommending to use GCC, which is default compiler on Linux distributions. GCC supports cross-compilation natively, but to achieve this you need to build entire kit from scratch.

Short overview of the process:

  1. Compile install latest binutils with --target=x86_64-pc-freebsd10 configure argument.
  2. Get FreeBSD libs/includes and install them to the buildroot location
  3. Compile and install GMP with --host=x86_64-pc-freebsd10 configure argument.
  4. Compile and install MPC with --host=x86_64-pc-freebsd10 configure argument.
  5. Finally compile GCC, you will have to specify sysroot path and all the libs location. Also you will have to manually define #define HAVE_ALIGNED_ALLOC 1 due to bug in the GCC autoconf scripts.

I created Dockerfile with all steps mentioned. GCC natively supporting only libstdc++ which is not available on the FreeBSD. Below is an example:

root@96de1e64fff1:/# /opt/cross-freebsd/bin/x86_64-pc-freebsd10-c++ -o helo helo.cpp
root@96de1e64fff1:/# file helo
helo: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), dynamically linked, interpreter /libexec/ld-elf.so.1, for FreeBSD 10.3, not stripped
root@96de1e64fff1:/#  readelf -d helo|grep NEEDED
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.5]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.7]

You can override this by overriding this using compiler/linker flags:

root@96de1e64fff1:/# /opt/cross-freebsd/bin/x86_64-pc-freebsd10-c++ -std=c++11 -I/opt/cross-freebsd/x86_64-pc-freebsd10/usr/include/c++/v1 -nostdinc++ -nodefaultlibs -lc++ -lm -lc -lgcc_s -lgcc -o helo helo.cpp

root@96de1e64fff1:/# readelf -d helo|grep NEEDED
 0x0000000000000001 (NEEDED)             Shared library: [libc++.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libcxxrt.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.5]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.7]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]

Some comments on the commandline:

  • -std=c++11 -nostdinc++ needed to avoid errors on libc++ headers
  • -nodefaultlibs -lc++ -lm -lc -lgcc_s -lgcc will tell gcc to not use with a predefined libraries but to use libc++ instead.

Problems with GCC cross-build:

  • Entire build chain need to be built from scratch. Not a big issue if you are using containers, but still takes some time to maintain it.
  • Natively only libstdc++ is supported which is now not part of the FreeBSD installation. To workaround this you can provide it with your code or to build static. It is possible to use libc++ instead but this required a lot of custom flags and may not work reliable.
  • Natively FreeBSD is not using gcc (with only exception of some ports), so behavior of the resulted code may be different due to some compiler differences.

Using Clang

After all i decided to take another approach. Clang is now (starting from FreeBSD 10) default compiler, as well as libc++ is used instead of libstdc++. Great news is that clang already supports cross-compilation without need to completely recompile entire toolset. I been able to get working FreeBSD binaries on default Ubuntu LTS buildhost.

Below is a part of the Dockerfile to fetch required headers and libraries, install them to /opt/cross-freebsd-10 and fix broken links:

# Get FreeBSD libs/headers, extract and fix broken links
RUN cd /tmp && wget http://ftp.plusline.de/FreeBSD/releases/amd64/10.4-RELEASE/base.txz \
&& mkdir -p /opt/cross-freebsd-10 \
&& cd /opt/cross-freebsd-10 \
&& tar -xf /tmp/base.txz ./lib/ ./usr/lib/ ./usr/include/ \
&& cd /opt/cross-freebsd-10/usr/lib \
&& find . -xtype l|xargs ls -l|grep ' /lib/' \
| awk '{print "ln -sf /opt/cross-freebsd-10"$11 " " $9}' \
| /bin/sh && \
rm -f /tmp/base.txz

Now it is possible to create FreeBSD binaries using additional compiler flags: -target x86_64-unknown-freebsd10.0 --sysroot=/opt/cross-freebsd-10/


root@4f9b820ae1c9:/# clang++ -target x86_64-unknown-freebsd10.0 --sysroot=/opt/cross-freebsd-10/ -o helo helo.cpp
root@4f9b820ae1c9:/# file helo
helo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /libexec/ld-elf.so.1, for FreeBSD 10.4, not stripped

Resulted binary will run on FreeBSD host only.

Lets check dependencies:

root@4f9b820ae1c9:/# readelf -d helo|grep NEEDED
0x0000000000000001 (NEEDED) Shared library: [libc++.so.1]
0x0000000000000001 (NEEDED) Shared library: [libcxxrt.so.1]
0x0000000000000001 (NEEDED) Shared library: [libm.so.5]
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.7]

As we could see dependencies are fine and matching normal FreeBSD 10.4 installation. Of course you may have many buildroots to simplify multiply targets.


It was found that both GCC and CLANG are able to cross-compile to FreeBSD using Linux host. However, CLANG require less efforts for this, mostly due to implemented -target switch and natively supported libc++ used by the FreeBSD project. I think it is better to use it for this task, with only exception if your source code has some strict GCC dependencies.

I implemented cross compilation to the FreeBSD as part of the CircleCI tests for the smartomontools project. I am planning to play with other targets (ARM, PPC) as well.

Tagged ,

Network FreeBSD boot using Mikrotik router TFTP

Sometime bad things happens

One of my FreeBSD servers failed to boot after number of power outages on the
remote site. However IPMI was still working, so I been able to see remote state.

Problem was with damaged ZFS root pool, so single user mode was also not able to boot.

At this location i also had Mikrotik router connected to the same switch, so i
decided to try to fix this server using network (PXE) boot.
It was a challenge for me, because i never tried to boot from the
RouterOS device and was not sure if it will work at all.

Configuring Mikrotik DHCP server

To get network boot possible there are 2 minimal pre-requirements:

  • DHCP server to provide DHCP responses with a special parameters
    (file to boot).
  • TFTP server to host files required for network boot.

Normally FreeBSD also wants NFS for the remote booting, but in this case we will avoid it.

To configure DHCP on the Mikrotik i created new DHCP server and dedicated DHCP network for it, which includes boot-file-name property:

/ip dhcp-server
add interface=MYLAN name=tftpsever

/ip dhcp-server network
add address= boot-file-name=gpxelinux.0 dns-server= \
    gateway= netmask=24

Also static lease for the server ethernet card MAC address been added:

/ip dhcp-server lease
add address= always-broadcast=yes mac-address=\
      E4:1F:13:6C:12:34 server=tftpsever

Also i would recommend to enable DHCP logging to monitor status of the requests.

Configuring Mikrotik TFTP server

When DHCP configuration is completed, it is time to enable and configure TFTP on
the router. In the RouterOS you have to specify parameters for every hosted file.

Below is my configuration:

/ip tftp
add real-filename=gpxelinux.0 req-filename=gpxelinux.0
add real-filename=chain.c32 req-filename=chain.c32
add real-filename=pxelinux.cfg/default req-filename=\
add real-filename=memdisk req-filename=memdisk
add real-filename=menu.c32 req-filename=menu.c32
add real-filename=reboot.c32 req-filename=reboot.c32
add allow-rollover=yes real-filename=\
    mfsbsd-11.0-RELEASE-amd64.img req-filename=mfsbsd-11.0-RELEASE-amd64.img

Creating bootable mfsbsd installation on the TFTP server

To recover FreeBSD i decided to use mfsbsd project. This is FreeBSD live CD/USB
which loads to the memory (so no NFS needed) and provides a lot of useful tools
out of the box. Also due to it small size it was possible to fit it on the router flash memory, so no additional downloads or mounts were needed. And, of course, mfsbsd contains everything needed to repair broken ZFS pool. I been using USB image based on 11.0 RELEASE.

But mfsbsd alone is not enough – to boot from network we also need files from the syslinux project. I been using [outdated] version 4.04, just because i had some projects using it in the past and it seems that some modules were renamed in the recent version.

List of the files to upload:

gpxelinux.0 (syslinux-4.04/gpxe/gpxelinux.0)
chain.c32 (syslinux-4.04/com32/modules/chain.c32)
memdisk (syslinux-4.04/memdisk/memdisk)
menu.c32 (syslinux-4.04/com32/menu/menu.c32)
reboot.c32 (syslinux-4.04/com32/modules/reboot.c32)
mfsbsd-11.0-RELEASE-amd64.img (USB image from the mfsbsd web site)
pxelinux.cfg/default (configuration, see below)

To work properly with IPMI and boot mfsbsd image we need to create configuration. Line serial 1 115200 is to define serial port output, and menu ui is mostly to ensure that something useful is going on.

Content of the pxelinux.cfg/default file:

serial 1 115200
console 1
ui menu.c32
menu title Utilities

label mfsbsd
  menu label mfsBSD
  kernel memdisk
  initrd mfsbsd-11.0-RELEASE-amd64.img harddisk raw

Files needs to be copied to the router flash, i used sftp tool to upload them.

Configuring server

Make sure that you have enabled PXE boot in the BIOS (i been able to do this
using IPMI). Also, at least on my server, i had to enable not only Legacy PXE
(which is in use), but also UEFI IPMI or network boot was never starting.
If everything is done correctly you should see DHCP request followed by TFTP.
In my case it was 2 times:

  1. UEFI PXE, which failed because i am using legacy boot.
  2. Legacy PXE which succeed. After loading all required files boot menu was
    shown and i been able to select mfsbsd item.
  3. After selecting this item FreeBSD started to load, but no output been shown
    on IPMI console. This is because by default mfsbsd is not showing anything on the
    serial console. It should be easy to fix, but in my case it was not needed.

In a few minutes i found another DHCP request in the Mikrotik logs, this time
from the mfsbsd, and been able to reach the server using SSH (credentials are root/mfsbsd). It is possible to use RouterOS ssh client (/system ssh user=root

Finally fixing ZFS pool

My attempt to import failed zroot pool from the mfsbsd failed as well. Moreover –
zpool import -F zroot also been not working. Before giving up i been able to find non-documented -X switch, which together with -F been able to completely restore pool in ~30 minutes! After reboot server been able to boot normally.


  • Check what needs to be done to create UEFI compatible FreeBSD network boot.
  • Create additional menu items for RescueCD.
  • Create mfsbsd which sends output to the serial port 2, to be compatible with IPMI.
  • Utilize latest syslinux instead of very outdated one.
Tagged , , ,

How to monitor NVMe drives in the OSX

NVMe support in OSX

After upgrade to the latest Macbook Pro i found that smartctl is not able to find any smart capable drive. This is because Apple replaces SATA SSD with NVMe one and old SMART API is not working (it is very ATA specific). Smartmontools itself includes NVMe support for the Linux, Windows and FreeBSD, so i decided to try to add it to the Darwin as well. However it was not as easy as expected – Apple did not published any source code or documentation about NVMe device support or monitoring. Moreover – there is no any tool in OSX to show such statistic and old tools from SDK are useless because of API Change.

Starting to search for the API provider

After looking on the file tree i have found good candidate: /System/Library/Extensions/NVMeSMARTLib.plugin. Its done more or less similar to the /System/Library/Extensions/SMARTLib.plugin/ which provides SATA/ATA SMART support. As i mentioned – there is no documentation, so i had to use otool, nm and lldb to deal with it. As expected, it was found that API is similar to the ATA one. You can get list of the symbols and functions using this command: nm NVMeSMARTLib | c++filt -p -i. So i tried to connect to it using modified example from SDK for the SMART. Tricky part was to find kIONVMeSMARTUserClientTypeID and kIONVMeSMARTInterfaceID values which are using by CFPLUGIN infrastructure in the IOKit to initialize API interface. Fortunately library comparing this data during runtime, so with disasm i been able to find them. After successful connect to the interface i been able to reconstruct missing headers and use some of the functions (see below)

What is working and what is not.

The most important functions SMARTReadData and GetIdentifyData are working and result is provided in the structures matching with NVMe standard. I was not able to get GetLogPage function running, probably it expects pointer to some structure with defined data. If apple will release any consumer of it it would be easy to find this out.

Also there are some other, unknown functions in this API: GetFieldCounters (always returns error), ScheduleBGRefresh (no parameters, returns ok), GetSystemCounters and GetAlgorithmCounters (some driver info? or vendor-specific log pages?). Another interesting finding was string “Sandisk 401Z128G-4p-MLC” in the SMART log page, so possibly this NVMe is originally from this vendor.

SmartMontools support

I am working to add limited NVMe support to the smartctl and smartd for OSX. Now i already have working prototype, but need to cleanup and refactor some code. I am planning to add this before the next release. Below is an output from my disk:

smartctl 6.6 2017-09-14 r4434M [Darwin 16.7.0 x86_64] (local build)
Copyright (C) 2002-17, Bruce Allen, Christian Franke, www.smartmontools.org


Model Number:                       APPLE SSD AP0512J
Serial Number:                      XXXXXX
Firmware Version:                   16.14.01
PCI Vendor/Subsystem ID:            0x106b
IEEE OUI Identifier:                0x000502
Controller ID:                      0
Number of Namespaces:               2
Local Time is:                      Wed Sep 20 08:56:36 2017 CEST
Firmware Updates (0x02):            1 Slot
Optional Admin Commands (0x0004):   Frmw_DL
Optional NVM Commands (0x0004):     DS_Mngmt
Maximum Data Transfer Size:         256 Pages

Supported Power States
St Op     Max   Active     Idle   RL RT WL WT  Ent_Lat  Ex_Lat
 0 +     0.00W       -        -    0  0  0  0        0       0

SMART overall-health self-assessment test result: PASSED

SMART/Health Information (NVMe Log 0x02, NSID 0x0)
Critical Warning:                   0x00
Temperature:                        33 Celsius
Available Spare:                    90%
Available Spare Threshold:          2%
Percentage Used:                    0%
Data Units Read:                    19,311,330 [9.88 TB]
Data Units Written:                 11,653,167 [5.96 TB]
Host Read Commands:                 50,388,833
Host Write Commands:                37,404,327
Controller Busy Time:               0
Power Cycles:                       2,320
Power On Hours:                     23
Unsafe Shutdowns:                   7
Media and Data Integrity Errors:    0
Error Information Log Entries:      0

Reconstructed API

If you want to play with the API yourself – you can use this header. Please let me know if you found how to use GetLogPage or any other useful information:

// NVMe definitions, non documented, experimental

// Constant to init driver
#define kIONVMeSMARTUserClientTypeID       CFUUIDGetConstantUUIDWithBytes(NULL,      \
                                        0xAA, 0x0F, 0xA6, 0xF9, 0xC2, 0xD6, 0x45, 0x7F, 0xB1, 0x0B, \
                    0x59, 0xA1, 0x32, 0x53, 0x29, 0x2F)

// Constant to use plugin interface
#define kIONVMeSMARTInterfaceID        CFUUIDGetConstantUUIDWithBytes(NULL,                  \
                    0xcc, 0xd1, 0xdb, 0x19, 0xfd, 0x9a, 0x4d, 0xaf, 0xbf, 0x95, \
                    0x12, 0x45, 0x4b, 0x23, 0xa, 0xb6)

// interface structure, obtained using lldb, could be incomplete or wrong
typedef struct IONVMeSMARTInterface

        UInt16 version;
        UInt16 revision;

                // NVMe smart data, returns nvme_smart_log structure
        IOReturn ( *SMARTReadData )( void *  interface,
                                     struct nvme_smart_log * NVMeSMARTData );

                // NVMe IdentifyData, returns nvme_id_ctrl per namespace
        IOReturn ( *GetIdentifyData )( void *  interface,
                                      struct nvme_id_ctrl * NVMeIdentifyControllerStruct,
                                      unsigned int ns );

                // Always getting kIOReturnDeviceError
        IOReturn ( *GetFieldCounters )( void *   interface,
                                        char * FieldCounters );
                // Returns 0
        IOReturn ( *ScheduleBGRefresh )( void *   interface);

                // Always returns kIOReturnDeviceError, probably expects pointer to some
                // structure as an argument
        IOReturn ( *GetLogPage )( void *  interface, void * data, unsigned int, unsigned int);

                /* GetSystemCounters Looks like a table with an attributes. Sample result:

                0x101022200: 0x01 0x00 0x08 0x00 0x00 0x00 0x00 0x00
                0x101022208: 0x00 0x00 0x00 0x00 0x02 0x00 0x08 0x00
                0x101022210: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x101022218: 0x03 0x00 0x08 0x00 0xf1 0x74 0x26 0x01
                0x101022220: 0x00 0x00 0x00 0x00 0x04 0x00 0x08 0x00
                0x101022228: 0x0a 0x91 0xb1 0x00 0x00 0x00 0x00 0x00
                0x101022230: 0x05 0x00 0x08 0x00 0x24 0x9f 0xfe 0x02
                0x101022238: 0x00 0x00 0x00 0x00 0x06 0x00 0x08 0x00
                0x101022240: 0x9b 0x42 0x38 0x02 0x00 0x00 0x00 0x00
                0x101022248: 0x07 0x00 0x08 0x00 0xdd 0x08 0x00 0x00
                0x101022250: 0x00 0x00 0x00 0x00 0x08 0x00 0x08 0x00
                0x101022258: 0x07 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x101022260: 0x09 0x00 0x08 0x00 0x00 0x00 0x00 0x00
                0x101022268: 0x00 0x00 0x00 0x00 0x0a 0x00 0x04 0x00
                0x101022488: 0x74 0x00 0x08 0x00 0x00 0x00 0x00 0x00
                0x101022490: 0x00 0x00 0x00 0x00 0x75 0x00 0x40 0x02
                0x101022498: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
        IOReturn ( *GetSystemCounters )( void *  interface, char *, unsigned int *);

                /* GetAlgorithmCounters returns mostly 0
                0x102004000: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004008: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004010: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004020: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004028: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004038: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004040: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004048: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004050: 0x00 0x00 0x00 0x00 0x80 0x00 0x00 0x00
                0x102004058: 0x80 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004060: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004068: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004070: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004078: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004080: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004088: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004090: 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00
                0x102004098: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

        IOReturn ( *GetAlgorithmCounters )( void *  interface, char *, unsigned int *);
} IONVMeSMARTInterface;

Tagged , , , ,

Reassigning ESC key on the MacBook Pro with a touchbar

There is no escape

I like an idea of the touch-bar, but removing physical Esc button is nothing but a pain, especially if you are using vi/vim, but not only. I been touching it accidentally, closing some windows, etc. No way, really. So i had to find a way to remap.


OSX allows to remap Escape key, but its possible to use only few options (e.g. to use Caps instead). For me it was not looking like a good option. So i started to look for an alternative and found it.
Karabiner Elements allows you to remap any key. You can find name of the key in the Event Viewer. For me it was non_use_backslash (which is The plus-minus key “±”) to the escape.

Removing touch-bar Escape

To make touch-bar escape harmless i just re-assigned it to the “right shift” button. That`s how my my final configuration looks like:

That`s it. Remapping works in the GUI and terminal programs, no problems found so far. Interesting what key will be removed in the next model.


ZPA ZE312 counter RF output descriptopn

Some time ago i been able to read data from the ZPA ZE312 power meter, which seems to be popular in CZ. Data now automatically imported to Arduino every 5 minutes, i am planning to make a blog post about it. For now – just a description of the values which this device returns:

 'C.1.0(XXXXXXXXX)' # Meter serial number
 '0.0.0(XXXXXXXXXXXX)' # customer number = barcode
 '0.3.0(10000*imp/kWh)' # Active energy meter constant [imp/kWh]
 'F.F(000000)' # Error code
 '1.8.0(0010332.677*kWh)' # Energy a in total: a = |+a|+|-a|
 '1.8.1(0005757.422*kWh)' # Positive active energy (A+) in tariff T1 [kWh]
 '1.8.2(0004575.255*kWh)' # Positive active energy (A+) in tariff T2 [kWh]
## Registers of active energy per phases
 '21.8.0(0008995.585*kWh)' # Positive active energy (A+) in phase L1 total [kWh]
 '41.8.0(0001014.588*kWh)' # Positive active energy (A+) in phase L2 total [kWh]
 '61.8.0(0000322.503*kWh)' # Positive active energy (A+) in phase L3 total [kWh]
 '2.8.0(0000000.000*kWh)' # Negative active energy (A+) total [kWh]
 '22.8.0(0000000.000*kWh)' # Negative active energy (A-) in phase L1 total [kWh]
 '42.8.0(0000000.000*kWh)' # Negative active energy (A-) in phase L2 total [kWh]
 '62.8.0(0000000.000*kWh)' # Negative active energy (A-) in phase L3 total [kWh]
 'C.8.1(0207212145)' # Operating period of tariff register t1. Format RRMMDDhhmm, RR-year, MM-month, DD-date, hh-hours, mm-min.
 'C.8.2(0103181249)' # Operating period of tariff register t2. Format RRMMDDhhmm, RR-year, MM-month, DD-date, hh-hours, mm-min.
 'C.8.0(0305140414)' # Operating period in total +a. Format RRMMDDhhmm, RR-year, MM-month, DD-date, hh-hours, mm-min.
 'C.82.0(0000000000)' # Operating period in total -a. Format RRMMDDhhmm, RR-year, MM-month, DD-date, hh-hours, mm-min.
 'C.7.1(00000001)' # the number of power failures in phase L1
 'C.7.2(00000001)' # the number of power failures in phase L2
 'C.7.3(00000002)' # the number of power failures in phase L3
 '0.2.1(ver.02,100830,B526)' # SW version
 'C.2.1(1112221302)' # Date and time of the last parameterization. Format RRMMDDhhmm, RR-year, MM-month, DD-date, hh-hours, mm-min.
 'C.2.9(1112221302)' # Date and time of the last read-out. Format RRMMDDhhmm, RR-year, MM-month, DD-date, hh-hours, mm-min.
 'C.3.9(0000000000)' # number of trials of attacking by magnetic field (scaler, counter)
Tagged , ,

Home Automation – WIFI connected FAN in the bathroom with humidity sensor

Why do i need that?

To avoid mold in the house it needs to be properly ventilated. Bathrooms are in the danger zone, because of the high humidity and temperature. Some time ago i bough a ventilator which automatically turns on/off based on the humidity, but it was never working good. It has variable resistors to tune it + some complex analog electronic and in fact was never working as intended and in about 1 year controller completely died. So, i had to find a replacement and decided that this is a perfect task for the HA. First part of the task was to define the requirements:

  • Ventilator must be able to turn on when some humidity value is reached (e.g. 80%) and turn off on low threshold (e.g. 60%)
  • It should be easy to change this thresholds. I would expect that its possible for them to be different at winter/summer seasons, may be not.
  • It would be great to monitor Temperature/Humidity in the bathroom with the same device and report it to the DomoticZ server.
  • Additionally it should be possible to start ventilator by pressing a button (e.g. to test if it works or for manual action)
  • And to have a lot of fun it should be built on cheap and open source components.

Choosing the right platform

Initially i had an idea (and prototype) on the Arduino Nano + Relay board + DHT21 sensor + 5V power supply + some connectivity board (wifi or bluetooth). However, i found that there will be a lot of connected components + wifi boards for the Arduino are very limited and sometime expensive, a lot of custom code needs to be written, etc. So i decided to try ESP8266 platform based devices.
One of the known devices based on this chipset is Sonoff TH10/TH16 (only relay is different, 10A and 16A):
sonoff TH10

I found controller on the Ali Express, because on the official web site it was out of stock.

What is inside?

  1. ESP8266 module which provides SoC with wifi connectivity. Serial port (3.3v, be careful!) soldered on the board, but no pins connected (but easy to add)
  2. Internal power supply circuit to power 3.3V SoC from the 220V power line
  3. Mini audio jack socket to connect different sensors. DHT21 and few others are available from the vendor. I ordered mine with DHT21 which is Humidity + Temperature (and is much better compared to DHT11)
  4. Relay (10 or 16A) connected to the SoC GPIO and manageable from it.

Flashing Sonoff TH16

Native firmare works using a cloud and is integrated with the vendor services. This was not something i would like to have, so i decided to re-flash it. There are many options for the ESP8266 (Arduino sketches, NodeMCU, firmware from vendor, etc). After quick research i decided to use an ESP Easy firmware based on such features:

  • It is free and opensource
  • Comes with a handy web interface and support of many different sensors, so very easy to start with.
  • Supports DomoticZ, MTTQ and other connectivity options.
  • Allows limited scripting (Rules engine) to implement device-side logic

I decided to use version 2.0 (ESP Easy Mega) which is still under development, because release was lacking some of the functionality i need. It is very easy to flash firmware to the device – all you have to do is to solder 4 pins in front of the board, which are VCC, RX, TX, GND. To flash it you will need 3.3V TTL Serial converted with a 3.3v output to power the board.

⚠WARNING⚠!!! never try to flash the device when it is connected to the 220V powerline. There is no proper ground isolation and most likely it will kill your device, serial converter and PC. You should disconnect device from the 220v and power it from the TTL converted instead (or any other 3.3v source)

To flash the firmware i been using esptool.py tool and such command:

esptool.py -b 115200 --port /dev/tty.usbserial-A900ZKU9 write_flash 0x0000 ESPEasy_v2.0.0-dev8_normal_1024.bin

Device needs to be programmed in the flash mode, which is entered by powering on device with the button pressed.

Of course serial port name will depend on your OS and controller. After flash you should reboot the device and it will automatically create an SSID ESP_0 with a WPA password configesp. On address there is a web interface to setup connection to the home WIFI network.

EasyESP initial configuration

After Sonoff device is connected to the wifi you can access it from the browser. First think i would recommend to do is to assign blue led to the WIFI status indication. This could be done in the Hardware section, by setting wifi status led to GPIO-13 ESP Easy - status led. Next thing is to define devices available on the board: button (GPIO-0), and DHT21 sensor (GPIO-14). Additionally i defined dummy device, which will be later used in the rules engine:
ESPEasy - devices

To report values to the DomoticZ controller you have to define it in the controller section:
Controller. Additionally you will have to create virtual devices (using virtual switch) in the DomoticZ for the Temperature + Humidity sensor and the switch. ID in devices section should match one from DomoticZ. Also you may want to setup syslogd ip to see logs from the device when not using serial. This is important, because in my case DHT sensor was not working properly when powered from TTL converter (properly not enough current) but its not possible to use serial when 220V is connected.

EasyESP rules

To avoid any dependency on DomoticZ controller i decided to put this simple logic in the device. There is a Rules engine (it needs to be enabled in the Advanced menu) which allows to define some simple device-side rules. It is very limited, but for my task it is enough. Some of the features i been using:

  • Virtual device as variable holder and event loop provider. Every virtual device allow to store up to 4 values and creates own event loop (loop time is set by “Delay” value).
  • Timers, which allows to call user-defined callback on specified timeout (and set a new one if needed)
  • Events from the device (button, temperature sensors, etc). Events can be used in the rules.
  • System startup event to define actions on a startups.
  • Simple (no nested) if/else controls.
  • Many functions which can be called from the code, e.g. to manager GPIO, do HTTP calls, etc.

Below is a ruleset i created. It defines threshold to turn on/off, reports FAN status to the DomoticZ, supports button which turns on ventilator for a 3 minutes even if conditions are not met:

On System#Boot do
  TaskValueSet,3,1,75.00 // temperature on dummy sensor
  TaskValueSet,3,2,70.00 // humidity on dummy sensor
  event FanOff

// main loop
On OnOffValues#HumidityOn do
  If [Temp#Humidity]>[OnOffValues#HumidityOn]
    event FanOn
  If [Temp#Humidity]<[OnOffValues#HumidityOff]
    event FanOffIfNotTimer // workaround for no nestled if

On FanOffIfNotTimer do
  If  [OnOffValues#TimerActive]=0
    event FanOff

// timer button is pressed
On Button#Switch=0.00 do
  If [OnOffValues#FanOn]=0 // do nothing if FAN is already on
    event FanOn
    timerSet,1,180 // turn off in a 3 minutes

// turn off FAN after timer
On Rules#Timer=1 do
   If [Temp#Humidity]<[OnOffValues#HumidityOn] // do not turn off if Humidity is High
     event FanOff

// command to turn on fan
On FanOn do
  TaskValueSet,3,3,1 // FAN is on

// command to turn off fan
On FanOff do
  If [OnOffValues#FanOn]=1
    TaskValueSet,3,3,0 // FAN is off

Additionally i can turn on FAN from the DomoticZ using HTTP call to the http://bath.local/tools?cmd=event+Button%23Switch URL. I used this as “On Action” for the “Push On” button switch. Of course, nice looking visualization is available:
Temperature/Humidity graph


Everything works as it should, however some of the things i would like to improve in the feature:

  • Solution is not secured properly. I am planning to use own SSID for the HA devices to solve this problem, ESP Easy do have password protection, but it is to secure only web access, API calls are still not secured.
  • Thresholds can be easily changed remotely using TaskValueSet command call
  • Should threshold depend on the temperature or it is overkill? How long will DHT21 survive in such wet environment ? Subject to test… Anyway, its cheap.
  • May be MQTT is better compared to DomoticZ HTTP API? Any comments on that?

As always, comments and suggestions are welcome 🙂

Tagged , , , ,

Acmetool utility port for the FreeBSD

I am actively using Lets Encrypt certificates for my private and business projects. Initially i been using official Python client to obtain them, and it was all kind of possible problems. At some point i migrated to the Acmetool client which works perfectly and allows me to maintain hundreds of the certificates with a minimal efforts. This is feature list from the web site:

✅ Zero-downtime autorenewal
✅ Supports any webserver
✅ Fully automatable
✅ Single-file dependency-free binary
✅ Idempotent
✅ Fast setup

Only problem for me was lack of the FreeBSD port – yes, you can grab FreeBSD binaries from the author web site, install them somewhere to the /opt/, but its not a Jedi Path. So i had to create FreeBSD port, which after few months of aging was finally accepted to the tree. There are small changes in the port compared to the author build – paths are FreeBSD-style and builds should be repeatable (i hope, at least). As usual – feel free to send me PR-s, bugreports and suggestions.

Lets Encrypt!

Tagged , , ,

Reading DHT11 temperature sensor on Raspberry Pi under FreeBSD

Connecting sensor to the RPi

DHT-11 is a very cheap temperature/humidity sensor which is commonly used in the IoT devices. It is not very accurate, so for the accurate measurement i would recommend to use DHT21 instead. Anyway, i had DHT-11 in my tool box, so decided to start with it. DHT-11 using very simple 1 wire protocol – host is turning on chip by sending 18ms low signal to the data output and then reading 40 bytes of data. Details about the protocol could be found in the specification. To read data from the chip it should be connected to the power (5v) and gpio pin. I used pin 2 as VCC, 6 as GND and 11 as GPIO (it is GPIO17, see pinout):

FreeBSD support

There is no support for this device out of the box. I found some sample code on the github, see lex/freebsd-gpio-dht11 repository. This code was a good starting point, but soon i found 2 issues with it:

  1. Results are very unreliable, probably due to gpio decoding algorithm.
  2. Checksum is not validated, so sometime values are bogus.

Initially i was thinking to fix this myself, but later found kernel module for this purpose, 1 wire over gpio. This module contains DHT11 kernel driver (gpio_sw) which implements DHT-11 protocol in the kernel space and exporting /dev/sw0 for the userland. Driver compiles on FreeBSD11/ARM without any changes. Use make install to install the driver.

Putting all the things together

  • To specify GPIO pin with a sensor put hint.gpio_sw.0.pin=17 into /boot/loader.conf.
  • I found that gpio_sw.ko needs to be loaded after kernel initialization, or device is not created. So i have added /sbin/kldload gpio_sw.ko to the /etc/rc.local file.
  • To make /dev/sw0 available for the non-root users you should add devfs_system_ruleset="localrules" to the /etc/rc.conf and add into /etc/devfs.rules this section:
add path 'sw0' mode 0644
  • Sample program (test.c) shows non human-readable data, use something like printf("h:%d.%d %%, t:%d.%d C\n",Buf[0],Buf[1],Buf[2],Buf[3]); if you want to get humidity and temperature, e.g.
root@rpi-b:/home/freebsd/gpio_sw # ./test
h:22.0 %, t:12.0 C

That is it, after reboot you should have working /dev/sw0 device.

Solving problems with LUA

Final goal was to add this sensor to the domoticz software. It is using LUA scripting to extend it functionality, e.g. to obtain data from non-supported or non standard devices. So, i decided to read /dev/sw0 from the LUA. I wrote simple test script:

file = io.open ("/dev/sw0", "rb")
out = file:read (5)

However script was always returning nil and i had to use truss tool to understand the problem. This is part of the trace:

open("/dev/sw0",O_RDONLY,0666)             = 3 (0x3)
fstat(3,{ mode=crw-r--r-- ,inode=96,size=0,blksize=4096 }) = 0 (0x0)
ioctl(3,TIOCGETA,0xbfbfe28c)             = 0 (0x0)
read(3,0x2065b000,4096)              ERR#22 'Invalid argument'

As you could see – LUA trying to read 4096 bytes, despite the fact that we specified 4. And driver checks this and returns the error. To fix this i did a small patch to avoid error and make LUA happy:

-- gpio_sw/gpio_sw.c    2014-05-12 11:26:51.000000000 +0000
+++ gpio_sw.mod/gpio_sw.c   2017-01-14 14:10:33.736813000 +0000
@@ -273,9 +273,10 @@
   duprintf("read - start, uio_resid=%i\n", uio->uio_resid);
   struct gpio_sw_softc *sc = cdev->si_drv1 ;
-  if ( uio->uio_resid >= sc->BufSize) return EINVAL ;
+  if ( uio->uio_resid >= sc->BufSize) sc->Len=sc->BufSize-1 ; // to work with buffered IO
+  else sc->Len=uio->uio_resid;
   if ( ! sc->GpioStatus) return ENXIO ;
-  sc->Len = uio->uio_resid ;
   int i = 0 ;
   while ( i < sc->Len)

After modification script works as it should:

[root@rpi-b /home/freebsd]# lua52 test.lua

Now chip is connected to the domoticz and reporting actual temperature/humidity.

Tagged , , ,

Domoticz on RPi 1 using FreeBSD/ARM

FreeBSD, Rasberry and Domoticz

I decided to create a Home Automation controller from my old Raspberry 1 device. In the past i used Linux on it but now it is supported by FreeBSD/ARM, so i decided to give it a try.

Installing FreeBSD

This is an easiest part. RPi 1 is fully supported, so i just downloaded FreeBSD11 image and put it on the SD card using dd tool. That`s it, FreeBSD boots normally (no installation process required) and is ready to use. I only added ntpd/sshd to the autostart process.

Installing domoticz

Domoticz is an open source home automation system, written on C++. There is a FreeBSD port for it, so first thing i tried to do was to install binary package using pkg tool. Unfortunately there is no binary package, so i had to install ports. To save some time before starting domoticz compilation i would recommend to install all build requirements using pkg manager, compiling them on RPi 1 would take a lot of time. Some tips on getting domoticz compiled on RPi 1:

  • Clang/C++ is using a lot of memory, so swap is required. I added swap space on the connected USB storage, with 512Mb swap file.
  • Compilation process will take a lot of hours. Probably cross-compilation would be much faster, but then it wont be a normal port build. Anyway, this process is a good crash test for the hardware.
  • I found that build failed on a 2 files: hardware/I2C.cpp and hardware/PiFace.cpp. This is the reason why there is no binary package. To fix the build – replace __arm__ with __linuxarm__. This will fix the build process, issue is reported to the upstream and FreeBSD maintainer.

Using domoticz

I did not found anything platform-specific for the domoticz on the FreeBSD. After installation i only imported database from the OSX installation and copied my LUA scripts for the custom sensors + installed required LUA modules from the ports. Despite the ugly build performance domoticz works very fast and using just about 25Mb of RAM. Currently I2C support in the hardware/I2C.cpp is implemented only for Linux/ARM, i am planning to fix that in a few weeks (i do not have any supported i2c devices now). FreeBSD/ARM itself works very stable, i did not had any problems with it.

Tagged , ,

PPTP on OSX Sierra

After upgrading to OSX Sierra (10.12.2) my PPTP connections just disappeared. Also it is not possible to create new PPTP connections anymore. Yes, i know that PPTP protocol is old, could be insecure, but i still need it to connect to some legacy systems. And, after all, i don`t like when someone telling me what should i do 🙂

After quick research it was found that actually only GUI part is removed, but PPTP is still in the Darwin internals. You can use it using pppd tool from the command line (using sudo). Configuration should be placed into /etc/ppp/peers/ directory, e.g. /etc/ppp/peers/pptpvpn. Here is a sample config, which works for me:

        plugin PPTP.ppp
#        logfile /tmp/ppp.log
        remoteaddress gwvpn.example.com
        redialcount 1
        redialtimer 5
        idle 1800
        mru 1368
        mtu 1368
        novj 0:0
        user userid
        password userpw
        # used in ip-up script
        ipparam gwvpn 

To connect to the VPN use pppd call pptpvpn command. Use man pppd for the possible options in the config. Hopefully it will still be there some time 🙂

Tagged , , ,