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.
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?
- I want to learn Rust
- I want it to be as less configurable as possible
- I want it to run fast on my Raspberry Pi Zero W
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
&& && && &&
Install from crates.io
Compile from sources
Cross-compile for Raspberry Pi Zero W
File capabilities
You may need to manually set capabilities on the produced binary:
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
= 8080
[]
= "Clock"
= 2000
[]
= "Buienradar"
= 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
[]
= "Lua"
# language=lua
= '''
sendMessage("hello::wind", "READ_NON_LOGGED", {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
ts =
Buienradar
TODO
Clock
TODO
Nest
TODO
Telegram
TODO
Solar
TODO
2020-04-29 17:32:17,804 INFO [my_iot::core::persistence::thread] sun_vijfhuizen::before::sunset: ReadSnapshot Duration(12667.827000000001 s^1)
2020-04-29 17:32:17,806 INFO [my_iot::core::persistence::thread] sun_vijfhuizen::after::sunrise: ReadSnapshot Duration(40832.16 s^1)
Run at System Startup
For now please refer to Raspberry Pi systemd
page.
Example
[Unit]
Description = my-iot
BindsTo = network-online.target
After = network.target network-online.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
Logs
See also: How To Use Journalctl to View and Manipulate Systemd Logs.
Publish on the Internet with NGINX
Checklist
- Configure Let's Encrypt or another certificate provider
- Set right certificate and private key paths
- Generate
.htpasswd
or configure another way of authentication
Example
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;
}
}
}