Yet another home automation. Written in Rust.

At the moment all documentation resides in the README. It is incomplete and will likely stay incomplete until features will be stabilized. Nevertheless, I strive to maintain it.

Wiki rustdoc Crates.io Crates.io Build Status GitHub last commit

What Is It?

I'm writing something similar to Home Assistant from ground up for my tiny set of Wi-Fi enabled devices.

And no, I didn't think about the project name long enough.

Why?

Stack

Installation

There're few different ways to install My IoT. Either way, you get a single executable my-iot. Nothing else is needed.

Pre-compiled binaries for Raspberry Pi Zero W

bash wget `curl -s https://api.github.com/repos/eigenein/my-iot-rs/releases/latest | grep -o "https://github.com/eigenein/my-iot-rs/releases/download/.*/my-iot-arm-unknown-linux-gnueabihf"` -O ~/bin/my-iot.new && chmod +x ~/bin/my-iot.new && sudo setcap cap_net_raw+ep ~/bin/my-iot.new && mv ~/bin/my-iot.new ~/bin/my-iot && sudo service my-iot restart

Install from crates.io

bash cargo install my-iot

Compile from sources

bash git clone https://github.com/eigenein/my-iot-rs.git cd my-iot-rs make sudo make install

Cross-compile for Raspberry Pi Zero W

bash make docker/build/arm-unknown-linux-gnueabihf

File capabilities

You may need to manually set capabilities on the produced binary:

bash setcap cap_net_raw+ep /home/pi/.cargo/bin/my-iot

This is needed to use some low-level protocols (for instance, ICMP for the [[Ping Service]]) as a non-root user.

Settings

My IoT is configured with a single TOML file. By default, my-iot.toml is read from ~/.config directory.

Example

```toml http_port = 8080

[services.heartbeat] type = "Clock" interval_ms = 2000

[services.buienradar] type = "Buienradar" station_id = 6240 ```

Services

Service is a kind of interface between My IoT and the real world. You can set up as many services as you want, even multiple services of a same type. A service is typically capable of: - Producing messages about something is happening - Listening to other services messages and reacting on them

Lua

Lua service allows to react on incoming messages via a Lua script, so you can implement virtually anything.

Example

```toml [services.lua] type = "Lua"

language=lua

script = ''' sendMessage("hello::wind", "READNONLOGGED", {bft = 5})

function onMessage(message)
  info(string.format("%s: %s", message.sensor_id, message.value))
end

''' ```

onMessage

TODO

Builtins

My IoT adds some extra globals to execution context:

debug, info, warn and error

These functions are similar to the Rust's ones, but they only accept a single string literal as the only parameter. Whatever you pass there will go through My IoT logging and so is manageable by e.g. journalctl or whatever logging system you use.

function sendMessage(sensor_id, type, {args})

Sends out a message with the given sensor_id (string), type (see below) and arguments args (see below). You use this to control other services as well as provide custom sensors.

Possible message type-s are:

| Constant | | |---------------------|------| | "READ_LOGGED" | TODO | | "READ_NON_LOGGED" | TODO | | "READ_SNAPSHOT" | TODO | | "WRITE" | TODO |

Optional parameter args is a table, which may provide additional message details:

| Index | Type | | |-----------------------|---------|------| | room_title | string | TODO | | sensor_title | string | TODO | | timestamp_millis | number | TODO | | enable_notification | boolean | TODO |

All indices are optional. Also, a value is provided via either of the following indices in args:

| Index | Type | | |------------------|---------|-------------------------------------------------------| | bft | integer | Beaufort wind force | | counter | integer | Unsigned unit-less counter | | image_url | string | Image | | bool | boolean | true and false | | wind_direction | string | Point of the compass that represents a wind direction | | data_size | integer | Data size in bytes | | text | string | Plain text | | rh | number | Relative humidity in percents | | celsius | number | Temperature in degrees Celsius | | kelvin | number | Temperature in Kelvins | | meters | number | Length in meters |

TODO

Points of the Compass

TODO

Recipes

If Nest camera detects a motion, send an animation to Telegram

lua function onMessage(message) -- FIXME: `*::change` sensor may be removed in future. if message.sensor_id == "nest::camera::<camera_id>::animated_image_url::change" then sendMessage("telegram::<chat_id>::animation", "WRITE", {image_url = message.value}) end end

Send Nest camera snapshots to Telegram

lua sendMessage("telegram::<chat_id>::photo", "WRITE", { image_url = message.value, sensor_title = message.room_title, })

Buienradar

TODO

Clock

TODO

Nest

TODO

Telegram

TODO

Run at System Startup

For now please refer to Raspberry Pi systemd page.

Example

bash cat /lib/systemd/system/my-iot.service

```ini [Unit] Description = my-iot After = network.target

[Service] ExecStart = /home/pi/.cargo/bin/my-iot --silent WorkingDirectory = /home/pi StandardOutput = journal StandardError = journal Restart = always User = pi

[Install] WantedBy=multi-user.target ```

bash sudo systemctl enable my-iot sudo systemctl status my-iot sudo systemctl start my-iot sudo systemctl stop my-iot sudo systemctl restart my-iot

Logs

sh journalctl -f -u my-iot

See also: How To Use Journalctl to View and Manipulate Systemd Logs.

Publish on the Internet with NGINX

Checklist

Example

```nginx events { }

http { upstream backend { server 127.0.0.1:8081; keepalive 32; }

server {
    listen 443 ssl default_server;
    listen [::]:443 default_server;
    charset utf-8;

    add_header Strict-Transport-Security max-age=2592000;

    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
    ssl_certificate /etc/letsencrypt/live/example.com/cert.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    ssl_prefer_server_ciphers on;

    gzip on;
    gzip_buffers 16 8k;
    gzip_comp_level 6;
    gzip_http_version 1.1;
    gzip_min_length 256;
    gzip_proxied any;
    gzip_vary on;
    gzip_types
        text/xml application/xml application/atom+xml application/rss+xml application/xhtml+xml image/svg+xml
        text/javascript application/javascript application/x-javascript
        text/x-json application/json application/x-web-app-manifest+json
        text/css text/plain text/x-component
        font/opentype application/x-font-ttf application/vnd.ms-fontobject
        image/x-icon;
    gzip_disable "msie6";

    auth_basic "My IoT";
    auth_basic_user_file /etc/.htpasswd;

    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

} ```