GeistHaus
log in · sign up

smallhacks

Part of wordpress.com

Linux, FreeBSD, networking, etc.

stories
It’s always DNS
Defaultdns
Show full content
Problem with mail relay

Recently, I found in the Postfix logs on my personal mail server some suspicious temporary rejects,

with error

Client host rejected: cannot find your reverse hostname [212.132.99.84]

The email was eventually delivered after several retries. The root cause needs investigation.

The initial assumption was that Postfix was misconfigured. Reproduced the issue locally for further analysis.

# nslookup 212.132.99.84
;; Got SERVFAIL reply from ::1, trying next server
;; Got SERVFAIL reply from 127.0.0.1
** server can't find 84.99.132.212.in-addr.arpa: SERVFAIL

On the second or third try, the call was usually successful. I was also able to reliably reproduce the issue by running rndc flush && 212.132.99.84.

Let’s troubleshoot

First guess was that something was very wrong with DNS servers for the 84.99.132.212.in-addr.arpa. However, I did not spot any significant issues with dnsviz.net or dig +trace. The host’s recursive DNS resolver is BIND9 with some configuration changes.

So I decided to reproduce the issue locally by installing BIND 9.20 in a VM with a default configuration. The issue is easily reproducible:

# rndc flush && dig -x 212.132.99.84 @127.0.0.1

; <<>> DiG 9.20.22 <<>> -x 212.132.99.84 @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 62425

Looking at tcpdump, I see many queries to root DNS servers. This is an example of the dump:


    192.58.128.30.53 > 192.168.64.2.55622: 23480*- 2/13/3 e.ns.arpa. A 192.203.230.10, e.ns.arpa. RRSIG (1136)
08:50:59.176320 IP (tos 0x0, ttl 54, id 18377, offset 0, flags [none], proto UDP (17), length 1164)
    192.58.128.30.53 > 192.168.64.2.52994: 21314*- 2/13/3 h.ns.arpa. A 198.97.190.53, h.ns.arpa. RRSIG (1136)
08:50:59.176326 IP (tos 0x0, ttl 54, id 18378, offset 0, flags [none], proto UDP (17), length 1176)
    192.58.128.30.53 > 192.168.64.2.62054: 35468*- 2/13/3 g.ns.arpa. AAAA 2001:500:12::d0d, g.ns.arpa. RRSIG (1148)
08:50:59.176333 IP (tos 0x0, ttl 54, id 18379, offset 0, flags [none], proto UDP (17), length 1164)
    192.58.128.30.53 > 192.168.64.2.54688: 16143*- 2/13/3 g.ns.arpa. A 192.112.36.4, g.ns.arpa. RRSIG (1136)
08:50:59.176342 IP (tos 0x0, ttl 54, id 18381, offset 0, flags [none], proto UDP (17), length 1176)
    192.58.128.30.53 > 192.168.64.2.52248: 2148*- 2/13/3 e.ns.arpa. AAAA 2001:500:a8::e, e.ns.arpa. RRSIG (1148)
08:50:59.176353 IP (tos 0x0, ttl 54, id 18380, offset 0, flags [none], proto UDP (17), length 1176)
    192.58.128.30.53 > 192.168.64.2.64187: 49690*- 2/13/3 h.ns.arpa. AAAA 2001:500:1::53, h.ns.arpa. RRSIG (1148)

But surprisingly, there are no failed or successful requests to the rns.ui-dns.biz/rns.ui-dns.com/rns.ui-dns.org/rns.ui-dns.de which are authoritative servers for that record. The resolver never proceeds to the final query.

So let’s go debugging by adding logs:

logging {
    channel query_log {
        file "/var/log/named/query.log" versions 3 size 10m;
        severity debug 3;
        print-time yes;
        print-severity yes;
        print-category yes;
    };

    category queries { query_log; };
    category resolver { query_log; };
    category dnssec { query_log; };
};

This actually helps to find a root cause:

28-Apr-2026 08:59:17.655 resolver: debug 3: exceeded max queries resolving 'd.in-addr-servers.arpa/AAAA' (max-recursion-queries, querycount=50)
28-Apr-2026 08:59:17.655 resolver: debug 3: exceeded max queries resolving 'e.in-addr-servers.arpa/A' (max-recursion-queries, querycount=51)
28-Apr-2026 08:59:17.656 resolver: debug 3: exceeded max queries resolving 'e.in-addr-servers.arpa/AAAA' (max-recursion-queries, querycount=51, maxqueries=50)
28-Apr-2026 08:59:17.656 resolver: debug 3: exceeded max queries resolving 'f.in-addr-servers.arpa/A' (max-recursion-queries, querycount=51, maxqueries=50)
28-Apr-2026 08:59:17.656 resolver: debug 3: exceeded max queries resolving 'f.in-addr-servers.arpa/AAAA' (max-recursion-queries, querycount=51, maxqueries=50)
28-Apr-2026 08:59:17.656 resolver: debug 3: exceeded max queries resolving 'd.in-addr-servers.arpa/A' (max-recursion-queries, querycount=51, maxqueries=50)
28-Apr-2026 08:59:17.656 resolver: debug 3: exceeded max queries resolving '84.99.132.212.in-addr.arpa/PTR' (max-recursion-queries, querycount=51, maxqueries=50)
Changing configuration

BIND recently added query limits to avoid DDoS attacks.

It introduces a new option max-recursion-queries with default to 50. Let’s change it to 500 to see if that helps.

Test still returns SERVFAIL, but the log now shows a different error:

28-Apr-2026 09:09:57.084 dnssec: debug 3: validating 132.212.in-addr.arpa/NS: resuming validate_nx
28-Apr-2026 09:09:57.084 resolver: debug 3: exceeded global max queries resolving '84.99.132.212.in-addr.arpa/PTR' (max-query-count, querycount=201, maxqueries=200)

One more limit, this time maxqueries. It is controlled by the max-query-count option (default: 200). Once I set it to 500, BIND finally resolved the PTR record. This also explains why resolution eventually succeeds – BIND caches successful calls and will eventually be able to resolve the target record without hitting the limit.

What can be done to improve the things, and why we are there

BIND debug logs show that we are fetching 483 records for the very simple DNS PTR query. Contributing factors are IPv6 (A/AAAA duplications), DNSSEC (DS/DNSKEY), and queries to the root DNS servers.

BIND now supports mirroring of the root zone, so let’s try to enable it by replacing

zone "." {
  type hint;
  file "/usr/local/etc/namedb/named.root";
};

With

zone "." {
    type mirror;
};

After the restart, I repeated my test, and the number of fetches reduced from 483 to 99. This should also significantly reduce DNS resolution time/

Conclusion

From my POV, something is very wrong with the default BIND configuration and DNS protocol in general. By adding more and more functions, we now need hundreds of calls to do a very basic lookup. Root zone caching, as well as query limit fine-tuning, may fix (or hide) the issue, but the overall complexity of the DNS is not something I would expect to see. Also, errors like this are likely to go unnoticed in many systems, reducing Internet/DNS reliability and performance.

sammczk
http://smallhacks.wordpress.com/?p=1965
Extensions
MacOS: Accessing SMART information on USB NVMe enclosure
Defaultapplelinuxmacmacosnvmesmarttechnology
Recently, I purchased an NVMe drive and USB enclosure to do on-site backups of some large files from my Apple M3 Pro. The chipset is JMS-583. It gives around 1Gb/second transfer speed, which is not as good as new USB4/TB enclosures but fast enough for me. I wanted to see SMART data for the drive […]
Show full content

Recently, I purchased an NVMe drive and USB enclosure to do on-site backups of some large files from my Apple M3 Pro. The chipset is JMS-583. It gives around 1Gb/second transfer speed, which is not as good as new USB4/TB enclosures but fast enough for me.

I wanted to see SMART data for the drive inside, but it’s not as easy as it sounds. There are many contributing factors to it:

  • The enclosure itself exposes the device as USB Attached SCSI (UASP). MacOS supports this protocol and automatically detects the device. However, to get SMART data, you must send special commands, and there is no such API for the SCSI layer in MacOS.
  • There is a 3rd party kernel module for SAT devices, but it is outdated and will not work with NVMe where SNT (SCSI-NVME translation) is required. Also, Apple is now deprecating kernel modules, so it will likely stop working if it has not already done so.
  • This should not impact TB3/4 enclosures as they should expose device as NVMe disk, so OS SMART support should be able to use it natively. But i do not have one to test.

On Intel Macs, the easiest solution was to run a virtual machine and pass a USB device to it. However, virtualisation on Arm is significantly different, so i tested three different options and found that only one of the works:

  • UTM 4.6.4 – does not work, OS could see the disk but not able to expose it as SCSI device. I was also playing with qemu, which UTM uses, it producing a lot of warnings related to the libusb which seems to lack some of the functionality on macOS. There is an upstream bug already.
  • VirtualBox 7.1.4 – same as UTM, OS could see the disk, but not able to use it.
  • VMWare Fusion 13.6.2 – surprisingly works very well. Once OS could see device – one could use smartctl command to see SMART data. E.g. smartctl -d sntjmicron -a /dev/sda. So at the moment it seems to be only working option.

P.S. while working on it i found an issue SNT layer in the JMicron firmware. Workaround is already added to the SVN source by me.

sammczk
http://smallhacks.wordpress.com/?p=1943
Extensions
Installing FreeBSD 14 on IONOS VPS hosting
Defaultfreebsd
Show full content
About IONOS VPS

IONOS provides cheap Linux VPS hosting with full root access and unlimited traffic. It also provides a KVM console to administer out-of-band if needed. The only problem is that it does not support FreeBSD; the only available options are Linux flavors. However, I found that installing and using FreeBSD 14 using the mfsBSD project is possible. Internally, IONOS uses the Microsoft Hyper-V platform, which FreeBSD supports.

Initial setup

To install FreeBSD, we must choose any Linux option from the “Images” tab. I have chosen Ubuntu 22.04; however, any selection would likely work the same. Once installation is completed, go to the “Servers” tab, select your VPS, and use the “DVD Drive” menu, which allows you to boot the VPS from the recovery CD. Select “Debian 12” ISO and click "Load DVD.” Once done – open KVM using the “Actions -> Open Remote Console” menu. Go to the shell using the “Advanced Options -> Recovery” menu. Choose “Do not use a root file system” in the filesystem dialog and execute a shell. It will be used to install mfsBSD.

mfsBSD installation

First, we will install mfsBSD which would allow us to boot and install FreeBSD 14 using KVM.

From the Debian console download and write to the VPS disk mfsBSD 14 image:

wget https://mfsbsd.vx.sk/files/images/14/amd64/mfsbsd-14.0-RELEASE-amd64.img
dd if=mfsbsd-14.0-RELEASE-amd64.img of=/dev/vda

Go to the IONOS cloud panel and eject “Debian” ISO. Reopen the Remote Console; you should see FreeBSD booting. There will be a significant (~1 minute) delay after USB init, but the system should start normally after some time.

FreeBSD installation

Once mfsBSD starts, you can normally ssh the server (if IP is already assigned) using root/mfsroot credentials. Now the partition table is corrupt, and we need to fix it using the gpart recover vtbd0 command.

Run the bsdinstall command and use the vtbd0 disk to set up the system. I recommend removing original freebsd-ufs partition to avoid any traces of the mfsBSD setup. Follow the installation process normally, and use DHCP as a network option. Once installation is done, reboot the system, and FreeBSD 14 should start normally.

Reducing boot time

This is an optional step that impacts only boot time. FreeBSD spends significant time in USB stack initialization, which adds about 1 minute to the boot process. Also, it loads many “emulated” devices, such as floppy or serial controllers. According to kern.boottrace.log, the total measured time is 113579 msecs, about 2 minutes. To reduce this time, it is possible to use minimal kernel configuration.

First, we will need to install the src component and update the system:

fetch http://ftp.freebsd.org/pub/FreeBSD/releases/amd64/amd64/14.0-RELEASE/src.txz
tar -C / -xvf src.txz
freebsd-update fetch && freebsd-update install

Now let’s install MINIMAL kernel configuration:

cd /usr/src/
make buildkernel -j `sysctl -n hw.ncpu` KERNCONF=MINIMAL && make installkernel KERNCONF=MINIMAL

The ufs module must also be added to the /boot/loader.conf; otherwise, the system will not be able to boot. This is my /boot/loader.conf content:

kern.boottrace.enabled=1
autoboot_delay=1
ufs_load="YES"

Once done – reboot the system. Load time should be reduced from ~2 minutes to 5 seconds!

Enabling IPv6 and fixing “Bogus Host Name” warning

IONOS uses DHCPv6+rtsol to assign IP address and routing configuration for the IPv6.

First IPv6 address needs to be added to the IONOS control panel. Once it is done – the DHCPv6 client needs to be installed: pkg install dual-dhclient-daemon. This is my network configuration in /etc/rc.conf file to support both IPv4 and IPv6 with dynamic addresses:

ifconfig_vtnet0="DHCP"
dhclient_program="/usr/local/sbin/dual-dhclient"
ipv6_activate_all_interfaces="YES"
ipv6_defaultrouter="fe80::1%vtnet0"

Another small issue i found was dhclient[48327]: Bogus Host Name option 12: My_VPS (My_VPS) message every few minutes in the log. This is related to non-RFC compliant hostname returned by the ionos DHCP server. To ignore it – add /etc/dhclient.conf with a following content:

interface "vtnet0" {
  ignore host-name;
}

and restart DHCP client using service dhclient restart vtnet0 command.

This is it. I will keep this post updated in case of any other problems or optmisations found.

sammczk
http://smallhacks.wordpress.com/?p=1897
Extensions
Updating FreeBSD on armv6 board (RPI-B)
Defaultarmembeddedfreebsdraspberry
One of my old home automation boards running ebusd is still using Raspberry PI 1 B SoC. FreeBSD is still perfectly supporting this hardware, however, due to being a Tier-2 platform, binary updates freebsd-update are not supported. Of course, one can download the new image, but this will mean re-installing and reconfiguring all the software, […]
Show full content

One of my old home automation boards running ebusd is still using Raspberry PI 1 B SoC. FreeBSD is still perfectly supporting this hardware, however, due to being a Tier-2 platform, binary updates freebsd-update are not supported. Of course, one can download the new image, but this will mean re-installing and reconfiguring all the software, which is time-consuming and painful. Also, the traditional “build from source” way will probably take forever on this tiny board and also could potentially destroy the SD card. So obvious alternative was cross-compilation.

To build kernel and world I used the amd64 host.

  1. Checkout FreeBSD source for the specific release you want to upgrade to (e.g. FreeBSD 13.2 in my case)

    git clone -b releng/13.2 https://github.com/freebsd/freebsd-src
  2. Compile FreeBSD kernel and world. Keep in mind, that RPI should use a non-generic kernel (RPI-B), otherwise the kernel will not boot.

    cd freebsd-src
    make -j 8 TARGET=arm TARGET_ARCH=armv6 KERNCONF=RPI-B buildkernel
    make -j 8 TARGET=arm TARGET_ARCH=armv6 buildworld

  3. It’s time to generate release packages:

    sudo make -C release/ TARGET=arm KERNCONF=RPI-B TARGET_ARCH=armv6 kernel.txz
    sudo make -C release/ TARGET=arm TARGET_ARCH=armv6 base.txz
    sudo make -C release/ TARGET=arm TARGET_ARCH=armv6 src.txz

  4. Copy txz files from /usr/obj/usr/home/srvadm/buildbsd/freebsd-src/arm.armv6/release/ to the target host, e.g. using scp, and unpack them. It’s recommended to copy /boot/kernel to the /boot/kernel.old before. If you plan to use GDB extract also “-dbg” archives.

    tar xf kernel.txz --clear-nochange-fflags --unlink -C /
    tar xf base.txz --clear-nochange-fflags --unlink --exclude etc/ --exclude var/ -C /
    tar xf src.txz --clear-nochange-fflags --unlink -C /
    etcupdate
  5. Now it’s time to reboot. Once booted – upgrade all ports using pkg upgrade command and delete old files using cd /usr/src && make delete-old && make delete-old-libs command.

That should be it, uname -a returns FreeBSD rpi 13.2-RELEASE-p1 FreeBSD 13.2-RELEASE-p1 releng/13.2-n254621-08b87f63a046 RPI-B arm output.

sammczk
http://smallhacks.wordpress.com/?p=1885
Extensions
Using EP-54V-150W as a home UPS
DefaultHardwaremonitoring
This tool also exports battery current, so I wrote a small script to grab it from the SSH and export to the format that telegraf expects:
Show full content

In my living area power outages are not common, however, they may happens in the middle of the meeting and also sometime there are series of on/off events, what is quit disruptive for the home network equipment. After all i decided to get UPS for my small rack. Most of my network devices (with exception of router) are PoE powered and PoE switch has 54V DC input already. So to avoid double AC->DC->AC conversation i was looking for the DC UPS with a 54V output. After all i decided to try Ubiquiti EP-54V-150W – it is DC UPS with WEB UI, SNMP and external battery support. Router was connected usign DC-DC converter.

Device description

The device is modular and comes from the shop with an AC module only. To use it as UPS another (DC) module needs to be added. The device could be connected to external batteries (2,3 or 4 serially connected) and 54V consumers (front door socket). The device could be managed using the web interface, SNMP, or ssh (username ubnt using 0 uid, so it is effectively root) using an ethernet port. Device firmware is using Linux kernel 2.6.32 and MIPS CPU with busybox and proprietary tools. It is possible to use internal or external chargers for the batteries.

Monitoring

Basic monitoring is provided in the web UI, however, it lacks some important fields, like charge/discharge current and historical data.

Also, it is not possible to set alerts in it. So after all I decided to integrate it with Grafana + InfluxDB which I already using for my smart home setup.

To get the data I started with SNMP option. MIB files are provided on the firmware download page and the snmpwalk tool shows that most of the fields I am looking for are exported. I am using the Telegraf tool to import SNMP data to InfluxDB. However, it was found that the battery charge/discharge current is not exported, so I had to find another way to export it.

After playing with the ssh console and reading forums I found a non-documented psuctl tool that exports a lot of data missing in the WEB/SNMP:

EP.v1.11.0# psuctl
Ubiquiti PSU control tool v0.1
Copyright 2006-2022, Ubiquiti Networks, Inc. 

This program is proprietary software; you can not redistribute it and/or modify
it without signed agreement with Ubiquiti Networks, Inc.

Usage: psuctl [options] 
	-p 	 disable/enable PSU
	-s 	 disable/enable PSU standby mode
	-c 	 disable/enable PSU charge mode
	-b 	 change battery unit count for DC/DC PSU w/ G0922
	-i		 display PSU information
	-o		 display output information
	-a		 display active alerts
	-A		 display alerts history

EP.v1.11.0# psuctl -i
PSU#0 [AC]:
  enabled: yes
  connected: yes
  standby: disabled
  charge: disabled
PSU#1 [DC]:
  enabled: yes
  connected: yes
  standby: enabled
  charge: enabled
  batteries [2]: 27.018V
    operational: yes
    charging: no
    temperature: 37C
    current: -0.010A

This tool also exports battery current, so I wrote a small script to grab it from the SSH and export to the format that telegraf expects:

#!/bin/sh

ssh -i /usr/local/etc/telegraf.d/id_rsa \
    -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=error \
    ubnt@psu.local psuctl -i 1 | \
    awk '/current:/ {gsub("A","",$2); print "psu_ssh,host=psu batt_current="$2}'

My Telegraf configuration is available on the github.

Once metrics are ingrested to InfluxDB i built a Grafana monitoring dashboard (source):

Testing

Finally, once monitoring was done I decided to test the UPS. I am using 2x26Ah batteries and my normal consumption is about 42-45W. Once I disconnected the AC power devices switched to the batteries. The battery voltage started to decrease and the discharge current was about 2A. After 7 Hours and 50 minutes UPS disconnected the batteries, rebooted, and started again, so probably it was cut-off value already and the batteries were empty.

Final pros/cons

Pros:

  • The device is small and could be placed in a standard rack
  • Easy to manage and monitor
  • Designed to run in “unattended” mode, no need to power on when AC recovers, etc

Cons:

  • It is not fanless and the AC module is noisy. Not a problem for me as it is in the basement, but still, for 150W it is an overkill. This could be fixed by the 2nd DC module and external PSU.
  • Charging current is only 7.5W, so it takes time to fully charge the batteries (in my case about 2.5 days until full charge)
  • Not possible to configure cut-off voltage, could be an issue if using non Lead-Acid batteries.
sammczk
http://smallhacks.wordpress.com/?p=1861
Extensions
Integration of the gas leakage detection to the smart home
DefaultIoT
Show full content

As I am (still) using a gas boiler I need to monitor gas leakage to ensure that if it happens I am aware before it caused any damage.

In the past, I was using "dumb" sensors with only sound/led indication of the leak. Now, as it was time to replace the old sensor anyway, I decided to try something more intelligent.

Some of the criteria I was using to find the device:

  • It should be able to work autonomously, with a sound leak alarm no matter what.
  • It should use standard WIFI. I already have coverage of the entire house with WIFI and I see very little value in using other wireless protocols and their gateways.
  • It should use open standards and should be able to operate without any kind of cloud connectivity. It is very important to me to have the device fully functional even if the vendor will decide to shut down its own cloud servers or "end of life" the current protocol.

After all, I found that the "Shelly Gas" sensor meets all the criteria – according to specification it supports MQTT, REST and can work without vendor app/cloud connectivity. So, I decided to give it a try.

Device experience

The device seems to be built on the Expressif platform (using Mongoose-OS) and once turned on provides an Access Point to connect. Once connected it is possible to configure the device using a web browser and switch it to client mode. I have a dedicated SSID for home IoT devices, without internet connectivity, so I decided to use it. After configuration device successfully acquired an IP address using DHCP and was available on it for the configuration.

There is not too much to configure – "eco mode", buzzer volume, and 3 actions. Actions are webhooks with user-defined URLs triggered on alarm status change (mild/heavy/gone). There are also settings to enable and configure MQTT and CoIoT protocols. It is also possible to control a smart gas valve with it, however, I do not have such.

Getting data from the device

I decided to integrate the device with Grafana, to build real-time graphs and use its alerting functionality. To push data in the InfluxDB I am already using Telegraf, so it was logical to integrate it with Shelly Gas.

The device itself has an HTTP server that serves UI and API. Vendor provides a very detailed description of the device API.

To grab device data I was using /status HTTP endpoint. Here is a sample reply:

{
  "wifi_sta": {
    "connected": true,
    "ssid": "IOT",
    "ip": "192.168.1.10",
    "rssi": -54
  },
  "cloud": {
    "enabled": false,
    "connected": false
  },
  "mqtt": {
    "connected": false
  },
  "time": "18:22",
  "unixtime": 1675617720,
  "serial": 59,
  "has_update": false,
  "mac": "10521CF2AAAA",
  "cfg_changed_cnt": 0,
  "actions_stats": {
    "skipped": 0
  },
  "gas_sensor": {
    "sensor_state": "normal",
    "self_test_state": "not_completed",
    "alarm_state": "none"
  },
  "concentration": {
    "ppm": 0,
    "is_valid": true
  },
  "valves": [
    {
      "state": "not_connected"
    }
  ],
  "update": {
    "status": "unknown",
    "has_update": false,
    "new_version": "",
    "old_version": "20221027-111245/v1.12.1-ga9117d3"
  },
  "ram_total": 52528,
  "ram_free": 39640,
  "fs_size": 233681,
  "fs_free": 92368,
  "uptime": 187828
}

What I like is that device provides all required data in a single HTTP call and uses JSON format, so it was very easy to integrate it.

Building Grafana dashboard

To get data from the device in the InfluxDB I used the following configuration:

[[inputs.http]]
  urls = [
   "http://192.168.1.10/status"
  ]
  tagexclude = ["url", "host"]
  data_format = "json_v2"
  [[inputs.http.json_v2]]
      measurement_name = "shellygas"
      [[inputs.http.json_v2.tag]]
          path = "wifi_sta.ip"
          rename = "ip"
      [[inputs.http.json_v2.tag]]
          path = "mac"
      [[inputs.http.json_v2.field]]
          path = "concentration.ppm"
          rename = "concentration"
          type = "int"
      [[inputs.http.json_v2.field]]
          path = "gas_sensor.sensor_state"
          rename = "sensor_state"
      [[inputs.http.json_v2.field]]
          path = "gas_sensor.alarm_state"
          rename = "alarm_state"
      [[inputs.http.json_v2.field]]
          path = "ram_total"
          type = "int"
      [[inputs.http.json_v2.field]]
          path = "ram_free"
          type = "int"
      [[inputs.http.json_v2.field]]
          path = "fs_size"
          type = "int"
      [[inputs.http.json_v2.field]]
          path = "fs_free"
          type = "int"

Once restarted Telegraf started to poll the device every 10s and push data to InfluxDB. Here is a sample dashboard I created:

The exported version could be downloaded from my GitHub.

Testing

To test the device I used dust-off compressed gas aerosol. Once the concentration is high enough device starts sound/audio indication and the PPM counter is also increasing. Here is a Grafana output of the test:

More things to do

Connect the device to the Domoticz using the MQTT protocol

  • Configure alerts for the device status and availability
  • Think about integration of the device alerts with SMS (using AWS SNS?) and email
Conclusion

I like the way Shelly built the device – it has cloud integration but it is optional. Support of open and well-documented protocols allowed me to easily integrate it into my setup.

sammczk
http://smallhacks.wordpress.com/?p=1850
Extensions
ZPA ZE314 OBIS
DefaultHardwareIoT
Show full content

Recently my energy counter was upgraded to ZPA ZE314. I found that reading data with IEC 62056-61 (OBIS) still works as expected, the only difference is that now I need to send not only '\x2F\x3F\x21\x0D\x0A' but also '\x06\x30\x30\x30\x0D\x0A' to start the transfer, so I will have to change my Domoticz integration.

Below is the meaning of the attributes, from different sources:

/ZPA5ZE314.v61_012
C.1.0(1234567890)                # Meter serial number
0.0.0(1234567890123456)          # Device address 1 (contract number)
0.3.0(10000*imp/kWh)             # Active energy meter constant (LED)
0.3.3(00100*imp/kWh)             # Active energy meter constant (S1)
F.F(000000)                      # Fatal error meter status
1.8.0(0000003.436*kWh)           # Positive active energy (A+) total [kWh]
1.8.1(0000001.165*kWh)           # Positive active energy (A+) in tariff T1 [kWh]
1.8.2(0000002.271*kWh)           # Positive active energy (A+) in tariff T2 [kWh]
1.8.1-0*(0000001.011*kWh)        # Historical data for the last 14 months
21.8.0(0000001.488*kWh)          # Positive active energy (A+) in phase L1 total [kWh]
41.8.0(0000000.980*kWh)          # Positive active energy (A+) in phase L2 total [kWh]
61.8.0(0000000.973*kWh)          # Positive active energy (A+) in phase L3 total [kWh]
2.8.0(0000001.010*kWh)           # Negative active energy (A+) total [kWh]
2.8.0-0*(0000001.010*kWh)        # Historical data for the last 14 months
22.8.0(0000000.336*kWh)          # Negative active energy (A-) in phase L1 total [kWh]
42.8.0(0000000.336*kWh)          # Negative active energy (A-) in phase L2 total [kWh]
62.8.0(0000000.337*kWh)          # Negative active energy (A-) in phase L3 total [kWh]
1.6.1(0000.30*kW,2301241630)     # Positive active maximum demand (A+) in tariff T1 [kW]
                                 # with date/time of the event (YYMMDDHHMM)
1.6.2(0000.48*kW,2301241745)     # Positive active maximum demand (A+) in tariff T2 [kW]
                                 # with date/time of the event (YYMMDDHHMM)
1.6.1-*(0000.00*kW,0000000000)   # Historical data for the last 14 months
                                 # with date/time of the event (YYMMDDHHMM)
2.6.0(0000.00*kW,0000000000)     # Negative active maximum demand (A-) total [kW]
                                 # with date/time of the event (YYMMDDHHMM)
2.6.0-*(0000.00*kW,0000000000)   # Historical data for the last 14 months
C.8.1(00000038)                  # total time T1
C.8.2(00000128)                  # total time T1
C.8.0(00000201)                  # total operation time
C.82.0(00000001)                 # Operating period in total (RRMMDDhh)
C.7.1(00000000)                  # PowerOutage_L1
C.7.2(00000000)                  # PowerOutage_L2
C.7.3(00000000)                  # PowerOutage_L3
C.7.1-*(00000000)                # Historical data for the last 14 months
C.7.2-*(00000000)                # Historical data for the last 14 months
C.7.3-*(00000000)                # Historical data for the last 14 months
0.2.1(ver.01, 180717, 3231)      # Parameters scheme ID
C.2.1(2206070830)                # Event parameters change - timestamp. 
C.2.9(2206070236)                # Date and time of the last read-out. 
0.9.2(230124)                    # Date (YY.MM.DD or DD.MM.YY)
0.9.1(180202)                    # Current time (hh:mm:ss)
C.51.15(0000000000)              # Event RTC (Real Time Clock) set - counter
82.8.1-*(2301241611)             # Terminal cover removal timestamps history (??)
C.6.0(0007260911)                # Time on battery
C.6.3(3.68V)                     # RTC battery voltage

sammczk
http://smallhacks.wordpress.com/?p=1837
Extensions
Converting TD-W8970B + Huawei e3372h to the WIFI LTE Router
Defaulthackinglinuxopenwrt
Show full content
Why to do that way

I had an old and long-time unused DSL router TP-Link TD-W8970B v1 collecting dust on my shelf. And USB LTE dongle from Huawei, E3372H which I rarely used as a backup connection. As TP-Link also had 2 USB ports I decided to convert it from DSL to LTE using this dongle.

Initially, I tried with stock firmware (the last update from 2014) and it was not working. It supports only old, 3G modems and AT based conversations. New USB modems are emulating network cards. So I decided to give the device the last chance and switch it to OpenWRT. It does not make a lot of sense from the practical point of view (routers are cheap) but it was a great practice 🙂

Broken serial port

To start the experiment I decided to start with the soldering serial port on the router. That was a bad surprise, none of my UART TTL adapters have been able to get any output from it. Led on the UART was blinking but nothing was on the terminal. I tried with 3 different UART/TTL to USB adapters with the same outcome, and as the loop test was passing just fine it was clear that the problem is with board hardware. So I decided to give it a last chance with a cheap USB Logic analyzer and PulseView as a UART decoder. Surprisingly it was working! Using 500Khz sample rate and default (115200/8N1) decoder settings I was able to read from the port. Likely analog circuit in the serial is kind of broken and not generating TTL levels expected by the UART adapter, but still readable with Logic Analyzer. So at least we have now the read-only port, hooray.

Hacking the router

As I had read only access to console I decided to use web interface hack method from the OpenWRT wiki. Idea is to modify the backup configuration which will force the router to run telnetd. I was not able to get it working with Python code, but the java app was working just fine. And I was able to telnet to the stock firmware and login as a root user. So I can try to install OpenWRT firmware to it 🙂

Bricking and debricking

As the article suggests I backed up all mtd (mtd0-mtd6) partitions to the FAT32 flash using the telnet interface. I decided to install OpenWRT by using hack from a similar deviceby writing part of the firmware to mtd1 and rest of it to the mtd2 using dd and cat command. Loader is living in mtd0, so we are not overriding it. For me the result was a broken router – it seems that this is not a reliable way to flash it. From UART I saw that u-boot fails to unpack the kernel image, which makes me think that flash was written with errors.

To de-brick the router I used the ch340 programmer. Initially, I desoldered flash (don’t do that :)) but later found that it perfectly works with a clip when soldered on the board. I flashed old firmware (by doing cat mtd0 mtd1 mtd2 mtd3 mtd4 mtd5 mtd6 > fw.bin) to the flash, soldered it back and the router started normally.

Finally flashing OpenWRT

As the router (with the telnet hack) was online again, I decided to flash OpenWRT using CH340. To generate an image I replaced mtd1 and mtd2 with OpenWrt squashfs image. Partition mtd2 was padded by zeros to ensure that size matched the original one. So I flashed it with fw-new.bin, enabled serial console monitoring and in a minute OpenWRT was started! I used the version from the article (15.05) as it was tested already and I have no idea if this ancient device is not broken in the recent builds. When the device started I was able to telnet to the 192.168.1.1 from the one port. I had to install some packages from the OpenWRT RNDIS user guide to make OS aware of the USB LTE Dongle. After reboot dongle was recognized as eth1, so I was able to assign it as a WAN device using Luci. I tested performance and it seems that I can get dongle speed (~30Mbit on this plan) using wifi, so the problem is solved and the device got a new life. Was it easier to just buy a new router? Likely yes. But here I had much more fun )

sammczk
http://smallhacks.wordpress.com/?p=1807
Extensions
Booting FreeBSD 13.1/arm64 on MacOS/arm64 using QEMU
Defaultapplearmfreebsd
Show full content

After upgrading to Mac M1 I decided to run FreeBSD virtually. As VirtualBox is not supported on this host (and likely will never be) QEMU was the only possible choice.

I found great gist post on how to do this with patched QEMU 6. The good news is that now QEMU 7 is released and it includes all proposed patches + some other related fixes. This note just updating the gist above for the latest QEMU and FreeBSD versions.

Also, I removed the custom edk2 download as it seems that supplied one is just good enough now.

Setup instructions

The easiest way to set up QEMU now is to use Homebrew. When done:

  • Install qemu with brew install qemu command

  • Create some directory to place the FreeBSD image and unpack it:

    mkdir -p ~/local/FreeBSD13 && cd ~/local/FreeBSD13
    wget http://ftp.cz.freebsd.org/pub/FreeBSD/releases/VM-IMAGES/13.1-RELEASE/aarch64/Latest/FreeBSD-13.1-RELEASE-arm64-aarch64.raw.xz
    unxz -k FreeBSD-13.1-RELEASE-arm64-aarch64.raw.xz
    
  • Finally create a shell script start.sh to run it:

    #!/bin/sh
    
    qemu-system-aarch64 \
      -M virt \
      -drive file=/opt/homebrew/share/qemu/edk2-aarch64-code.fd,format=raw,if=pflash,readonly=on \
      -accel hvf \
      -cpu host \
      -smp 4 \
      -m 8G \
      -drive file=FreeBSD-13.1-RELEASE-arm64-aarch64.raw,if=virtio,cache=writethrough,format=raw \
      -serial mon:stdio \
      -nographic
    

That should be it. Running start.sh should bring you into the FreeBSD console with a working network and SMP 🙂 Login is root, and the password is empty by default.

Resizing the root disk

If you want to add some disk space to the image it could be done easily. Stop FreeBSD and run this command on the host to add 2G to the image:

qemu-img resize FreeBSD-13.1-RELEASE-arm64-aarch64.raw +2G

Now boot FreeBSD normally. To grow root partition run such commands inside it:

gpart recover vtbd0
gpart resize -i 3 vtbd0
growfs /
Trying to build world and kernel

To check that system is fast and reliable enough I decided to rebuild the kernel/world on it.

fetch http://ftp.freebsd.org/pub/FreeBSD/releases/arm64/13.1-RELEASE/src.txz
tar -C / -xvf src.txz
make -j4 buildkernel
[skip]
>>> Kernel(s)  GENERIC built in 209 seconds, ncpu: 4, make -j4
make -j4 buildworld
[skip]
>>> World built in 2364 seconds, ncpu: 4, make -j4

As you could see both steps succeed in a reasonable time, which makes this option suitable for the development/testing.

Reducing host CPU usage

Only issue found is a high host CPU usage when FreeBSD guest is running. I was able to find solution for this in the freebsd-arm mail list. Line kern.hz=100 should be added to the /boot/loader.conf.

sammczk
http://smallhacks.wordpress.com/?p=1792
Extensions
Using AM2320B sensor with a FreeBSD/Raspberry Pi
HardwarefreebsdIoT
Show full content

Some time ago my DHT22 sensor, connected to the RPi1, died (humidity always was showing 99%), so I decided to replace it. I found a cheap AM2320 sensor that was in the same case as my old DHT, so I ordered it. When it arrived I found that its used 4 PINS instead of 3 and I2C protocol instead of simple 1-Wire in DHT. So I had to spend some time learning how to get the data from it.

Enabling I2C bus and connecting sensor.

In the config.txt located on FAT partition dtparam=i2c_arm=on line if its not there and reboot RPi. You should see something like that in the dmesg output:

root@rpi:/home/freebsd/ # dmesg | grep -i i2c
iicbus0:  on iichb0
iic0:  on iicbus0

If you see these lines it means that FreeBSD enabled RPi I2C bus on pins 3 (SDA) and 5 (SCL). You will also need to connect GND and VCC pins, I was using 3.3V, the chip should accept both according to the spec. See pinout on the picture:

Once connected you can run the scan command. This chip has an automatic sleep feature and does not respond to commands in this state, so the scan needs to be run twice:

root@rpi:~ # i2c -s ; i2c -s
Hardware may not support START/STOP scanning; trying less-reliable read method.
Scanning I2C devices on /dev/iic0: 
Hardware may not support START/STOP scanning; trying less-reliable read method.
Scanning I2C devices on /dev/iic0: 5c

As you could see – on the second run we got 0x5c address used by AM2320B.

Finally getting data from the sensor

As chip requires very strict timing before requests we cant use the i2c tool to get actual values from it. Also, the protocol has a checksum which we should calculate and data is collected as 16-bit integers. To get it in the human-readable form code needs to be written. And I found one for Linux at github.com/Gozem/am2320 repository. FreeBSD uses a slightly different way to work with I2C from userland (we need to use I2CRDWR ioctl) so I ported it to BSD and left all the logic the same. And it now finally works as expected:

root@rpi:~freebsd/libi2c # ./am2320
Temperature 16.1 [C]
Humidity    58.8 [%]
Domoticz integration

To work with domoticz i am using LUA scripting. I think I am going to port github.com/robbie-cao/i2c-lua to the FreeBSD to avoid calling external binary on every reading. It is also possible to do a tiny kernel driver for that, but it is not something I like, IMO userspace is a better space for it.

Update: 1-Wire mode and datasheet

After finishing this article and i2c code I found a datasheet for this chip. Chip support both i2c and old, legacy 1-wire when SCL is connected to Ground and SDA used as "data" line. Anyway, i2c seems to me more standard solution.

sammczk
http://smallhacks.wordpress.com/?p=1753
Extensions