tag:andres.svbtle.com,2014:/feedAndres Vahter2020-09-16T20:49:43-07:00Andres Vahterhttps://andres.svbtle.comandres.vahter@gmail.comSvbtle.comtag:andres.svbtle.com,2014:Post/release-rust-embedded-firmware-using-github-actions2020-09-16T20:49:43-07:002020-09-16T20:49:43-07:00Release Rust embedded firmware using Github Actions<h1 id="github-actions_1">Github Actions <a class="head_anchor" href="#github-actions_1">#</a>
</h1>
<p><a href="https://github.com/features/actions">Github Actions</a> is a nice way to setup CI/CD pipelines for your Github projects. Let’s setup it for an embedded firmware project written in Rust.</p>
<p>Continuous integration script would run every time when new code is pushed to <code class="prettyprint">master</code> branch and release script would run only if a new tag is pushed.</p>
<p>Release in Github looks like this:<br>
<a href="https://svbtleusercontent.com/cW2Uz9L7LcrxFVRdeCf1ky0xspap.jpg"><img src="https://svbtleusercontent.com/cW2Uz9L7LcrxFVRdeCf1ky0xspap_small.jpg" alt="release.jpg"></a></p>
<p>Let’s get started. We need following files in project root:</p>
<pre><code class="prettyprint lang-bash">.github/workflows/ci.yaml
.github/workflows/release.yaml
</code></pre>
<p>File names actually do not matter.</p>
<h1 id="ciyaml_1">ci.yaml <a class="head_anchor" href="#ciyaml_1">#</a>
</h1>
<p>It is assumed that your Cargo project is setup in such a way that you could just run <code class="prettyprint">cargo build --release</code> in root directory and everything builds. This script is doing just that:</p>
<pre><code class="prettyprint lang-yaml">name: Continuous Integration
on:
push:
branches:
- master
env:
CARGO_TERM_COLOR: always
jobs:
compile:
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable
steps:
- uses: actions/checkout@v2
- name: Install Rust ${{ matrix.toolchain }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
target: thumbv6m-none-eabi
override: true
- name: cargo build release
uses: actions-rs/cargo@v1
with:
command: build
args: --release
</code></pre>
<h1 id="releaseyaml_1">release.yaml <a class="head_anchor" href="#releaseyaml_1">#</a>
</h1>
<p>It is assumed that all tags are like <code class="prettyprint">0.9.1</code>, <code class="prettyprint">1.0.0</code> etc. So when such a tag is pushed following action will run:</p>
<pre><code class="prettyprint lang-yaml">name: Release
on:
push:
tags:
- '*.*.*'
env:
CARGO_TERM_COLOR: always
jobs:
compile:
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable
steps:
- uses: actions/checkout@v2
- name: Install Rust ${{ matrix.toolchain }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
target: thumbv6m-none-eabi
override: true
components: llvm-tools-preview
- name: Install cargo-binutils
uses: actions-rs/install@v0.1
with:
crate: cargo-binutils
version: latest
use-tool-cache: true
- name: Run cargo build --release
uses: actions-rs/cargo@v1
with:
command: build
args: --release
- name: Run cargo objcopy
uses: actions-rs/cargo@v1
with:
command: objcopy
args: --release --bin firmware -- -O binary firmware.bin
- name: Extract version from tag
id: version_tag
run: echo ::set-output name=TAG_VERSION::${GITHUB_REF#refs/tags/}
- name: Rename .elf and .bin files
env:
TAG_VERSION: ${{ steps.version_tag.outputs.TAG_VERSION }}
run: |
mv target/thumbv6m-none-eabi/release/firmware firmware-$TAG_VERSION.elf
mv firmware.bin firmware-$TAG_VERSION.bin
- name: Create release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: firmware*
tag: ${{ github.ref }}
overwrite: true
file_glob: true
</code></pre>
<p>This is a little bit more involved. Basically we would like to release following files if tag <code class="prettyprint">1.0.1</code> is pushed:</p>
<pre><code class="prettyprint lang-bash">firmware-1.0.1.elf
firmware-1.0.1.bin
</code></pre>
<p>Those files can be downloaded from Github release page.</p>
<h2 id="notes_2">Notes <a class="head_anchor" href="#notes_2">#</a>
</h2>
<p>It was kind of pain to strip <code class="prettyprint">1.0.1</code> part out of tag which is like <code class="prettyprint">refs/tags/1.0.1</code>. That is why there is separate step for that:</p>
<pre><code class="prettyprint lang-yaml">- name: Extract version from tag
id: version_tag
run: echo ::set-output name=TAG_VERSION::${GITHUB_REF#refs/tags/}
</code></pre>
<p>Later it can be used as an expression (do not confuse it with bash environment variable) :</p>
<pre><code class="prettyprint lang-bash">${{ steps.version_tag.outputs.TAG_VERSION }}
</code></pre>
<p>Notice that this script also installs <code class="prettyprint">llvm-tools-preview</code> and basically does <code class="prettyprint">cargo install cargo-binutils</code> which is needed to convert from <code class="prettyprint">.elf</code> to <code class="prettyprint">.bin</code>.</p>
<p>There you have it! </p>
tag:andres.svbtle.com,2014:Post/passing-messages-between-aws-iot-and-sqs-queue-using-lambdas-written-in-rust2020-07-09T13:40:30-07:002020-07-09T13:40:30-07:00Passing messages between AWS IoT and SQS queue using Lambdas written in Rust<p><a href="https://aws.amazon.com/iot-core/">AWS IoT Core</a> provides a convenient way to connect your IoT devices like ESP32 to the cloud. Typically MQTT protocol is used for that.</p>
<p>Let’s suppose everything is setup on AWS IoT side and you can see messages from IoT Test console. Next we would like to transfer those MQTT messages to some other service that can actually do something useful with them. <a href="https://aws.amazon.com/sqs/">AWS SQS</a> queues can be used for that.<br>
Let’s create a rule that triggers a Lambda function every time if something is sent to a topic <code class="prettyprint">floor/1/room/3/temperature</code>. Actually let’s receive all temperatures from all floors and rooms.</p>
<p>Rule query statement for doing it looks like this:<br>
<code class="prettyprint">SELECT *, topic() as topic FROM 'floor/+/room/+/temperature'</code>.<br>
We cannot just do<br>
<code class="prettyprint">SELECT * FROM 'floor/+/room/+/temperature'</code><br>
because then we lose information about topic which can be quite valuable.<br>
Let’s say temperature message itself is like this:</p>
<pre><code class="prettyprint">{
"timestamp": 1593493172
"temp": 20.6
}
</code></pre>
<p>Notice that rule query statement adds the <code class="prettyprint">topic</code> field and now JSON looks like this:</p>
<pre><code class="prettyprint">{
"timestamp": 1593493172
"temp": 20.6
"topic": "floor/4/room/1/temperature"
}
</code></pre>
<p>So this JSON is the event that our <code class="prettyprint">mqtt-to-sqs</code> Lambda function should expect.<br>
<a href="https://svbtleusercontent.com/7RL4P3p5zCHLFV4RdXhqCj0xspap.jpg"><img src="https://svbtleusercontent.com/7RL4P3p5zCHLFV4RdXhqCj0xspap_small.jpg" alt="mqtt-to-sqs.jpg"></a></p>
<h2 id="requirements_2">Requirements <a class="head_anchor" href="#requirements_2">#</a>
</h2>
<p>Setup Rust toolchain for cross compiling.</p>
<pre><code class="prettyprint lang-bash">rustup target add x86_64-unknown-linux-musl
# on macOS
brew install filosottile/musl-cross/musl-cross
# on macOS this is needed for openssl cross compiling
ln -s /usr/local/bin/x86_64-linux-musl-gcc /usr/local/bin/musl-gcc
</code></pre>
<h2 id="mqtttosqs_2">mqtt-to-sqs <a class="head_anchor" href="#mqtttosqs_2">#</a>
</h2>
<p>At the time of writing <a href="https://lib.rs/crates/lambda_runtime">lambda_runtime</a> has version number 0.2.1 and it is quite out dated and does not play well with <a href="https://github.com/rusoto/rusoto">rusoto</a> which is AWS SDK for Rust. We are going to use modern async version straight from the master.</p>
<h3 id="cargoconfig_3">.cargo/config <a class="head_anchor" href="#cargoconfig_3">#</a>
</h3>
<p>This configures cross compile target:</p>
<pre><code class="prettyprint">[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
[build]
target = "x86_64-unknown-linux-musl"
</code></pre>
<h3 id="cargotoml_3">Cargo.toml <a class="head_anchor" href="#cargotoml_3">#</a>
</h3>
<p>AWS expects that binary is called <code class="prettyprint">bootstrap</code>.</p>
<pre><code class="prettyprint lang-toml">[package]
name = "mqtt_to_sqs"
version = "1.0.0"
authors = ["Andres Vahter"]
edition = "2018"
# lambda exec must be called bootstrap
[[bin]]
name = "bootstrap"
path = "src/main.rs"
[dependencies]
tokio = { version = "0.2", features = ["macros"] }
lambda = { git = "https://github.com/awslabs/aws-lambda-rust-runtime/", branch = "master"}
once_cell = "1.4"
log = "0.4"
env_logger = "0.7.1"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
openssl = {version = "0.10.30", features = ["vendored"]}
rusoto_core = "0.44.0"
rusoto_sqs = "0.44.0"
</code></pre>
<h3 id="mainrs_3">main.rs <a class="head_anchor" href="#mainrs_3">#</a>
</h3>
<pre><code class="prettyprint lang-rust">use env_logger;
use log::{info};
use lambda::{handler_fn, Context};
use serde_json::{Value, json};
use rusoto_core::region::Region;
use rusoto_sqs::{SqsClient, Sqs, SendMessageRequest};
use once_cell::sync::OnceCell;
type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
fn sqs_client() -> &'static SqsClient {
static INSTANCE: OnceCell<SqsClient> = OnceCell::new();
INSTANCE.get_or_init(|| {
SqsClient::new(Region::EuNorth1)
})
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let _ = env_logger::try_init();
let func = handler_fn(func);
lambda::run(func).await?;
Ok(())
}
async fn func(mut message: Value, _: Context) -> Result<Value, Error> {
info!("message: {:?}", message);
let topic = message.as_object_mut().unwrap()
.remove("topic")
.ok_or("field 'topic' not available")?;
info!("topic: {:?}, message: {:?}", topic, message.to_string());
let message = json!({
"topic": topic,
"payload": message
});
let request = SendMessageRequest {
delay_seconds: Some(0),
message_attributes: None,
message_body: message.to_string(),
message_deduplication_id: None,
message_group_id: None,
message_system_attributes: None,
queue_url: "https://sqs.eu-north-1.amazonaws.com/1234567890/queue".to_string(),
};
sqs_client().send_message(request).await?;
Ok(json!("OK"))
}
</code></pre>
<p>Take a look at this:</p>
<pre><code class="prettyprint lang-rust">fn sqs_client() -> &'static SqsClient {
static INSTANCE: OnceCell<SqsClient> = OnceCell::new();
INSTANCE.get_or_init(|| {
SqsClient::new(Region::EuNorth1)
})
}
</code></pre>
<p>Basically it creates global <code class="prettyprint">SqsClient</code>. Otherwise it must be initialized with every Lambda call and it can take quite some time. So yes Lambdas actually are not always completely killed after execution and this trick saves significant amount of time. Without it Lambda was handled around 300 ms, with this trick it was around 10 ms.</p>
<p>This takes <code class="prettyprint">topic</code> field out of JSON.</p>
<pre><code class="prettyprint lang-rust">let topic = message.as_object_mut().unwrap()
.remove("topic")
.ok_or("field 'topic' not available")?;
</code></pre>
<p>..and creates a new JSON where <code class="prettyprint">topic</code> and <code class="prettyprint">payload</code> are nicely separated.</p>
<pre><code class="prettyprint lang-rust">let message = json!({
"topic": topic,
"payload": message
});
</code></pre>
<p>So Lambda input event <code class="prettyprint">mut message: Value</code>:</p>
<pre><code class="prettyprint lang-json">{
"timestamp": 1593493172
"temp": 20.6
"topic": "floor/4/room/1/temperature"
}
</code></pre>
<p>..becomes:</p>
<pre><code class="prettyprint lang-json">{
"topic": "floor/4/room/1/temperature"
"payload": {
"timestamp": 1593493172
"temp": 20.6
}
}
</code></pre>
<h2 id="sqstomqtt_2">sqs-to-mqtt <a class="head_anchor" href="#sqstomqtt_2">#</a>
</h2>
<p>Not much to add let’s just see how this code looks like other way around when we are receiving a message from some SQS queue and forward it to IoT service.</p>
<h3 id="cargotoml_3">Cargo.toml <a class="head_anchor" href="#cargotoml_3">#</a>
</h3>
<pre><code class="prettyprint lang-toml">[package]
name = "sqs_to_mqtt"
version = "1.0.0"
authors = ["Andres Vahter"]
edition = "2018"
# lambda exec must be called bootstrap
[[bin]]
name = "bootstrap"
path = "src/main.rs"
[dependencies]
tokio = { version = "0.2", features = ["macros"] }
lambda = { git = "https://github.com/awslabs/aws-lambda-rust-runtime/", branch = "master"}
aws_lambda_events = "0.3.0"
bytes = "0.5"
once_cell = "1.4"
log = "0.4"
env_logger = "0.7.1"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
openssl = {version = "0.10.30", features = ["vendored"]}
rusoto_core = "0.44.0"
rusoto_iot_data = "0.44.0"
</code></pre>
<h3 id="mainrs_3">main.rs <a class="head_anchor" href="#mainrs_3">#</a>
</h3>
<pre><code class="prettyprint lang-rust">use std::str::FromStr;
use env_logger;
use log::{info};
use lambda::{handler_fn, Context};
use aws_lambda_events::event::sqs::SqsEvent;
use serde_json::{Value, json};
use rusoto_core::region::Region;
use rusoto_iot_data::{IotDataClient, IotData, PublishRequest};
use bytes::Bytes;
use once_cell::sync::OnceCell;
type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
fn iot_client() -> &'static IotDataClient {
static INSTANCE: OnceCell<IotDataClient> = OnceCell::new();
INSTANCE.get_or_init(|| {
IotDataClient::new(Region::EuNorth1)
})
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let _ = env_logger::try_init();
let func = handler_fn(func);
lambda::run(func).await?;
Ok(())
}
async fn func(event: SqsEvent, _: Context) -> Result<Value, Error> {
info!("event: {:?}", event);
for record in event.records {
let message: Value = serde_json::from_str(&record.body.unwrap())?;
info!("message: {:}", message);
let topic = String::from_str(&message["topic"].as_str().unwrap())?;
let payload = serde_json::to_string(&message["payload"])?;
let request = PublishRequest {
payload: Some(Bytes::copy_from_slice(payload.as_bytes())),
topic: topic,
qos: Some(0),
};
iot_client().publish(request).await?;
}
Ok(json!("OK"))
}
</code></pre>
<p>Here it is expected that SQS event has the same JSON structure as defined before:</p>
<pre><code class="prettyprint lang-json">{
"topic": "some/topic",
"payload": "json message fields here"
}
</code></pre>
<h2 id="build-and-deploy_2">Build and deploy <a class="head_anchor" href="#build-and-deploy_2">#</a>
</h2>
<pre><code class="prettyprint">cargo build --release
zip -j rust.zip ./target/x86_64-unknown-linux-musl/release/bootstrap
</code></pre>
<p><code class="prettyprint">rust.zip</code> is the file that must be uploaded to Lambda. <a href="https://blog.knoldus.com/aws-lambda-with-rust/">Here</a> is nice tutorial how to do it.</p>
<p>That’s it! This is how you can write Rust Lambdas that are using some <a href="https://github.com/rusoto/rusoto">rusoto</a> modules.</p>
tag:andres.svbtle.com,2014:Post/parsing-line-based-protocol-using-rust-and-nom-12020-04-12T12:49:35-07:002020-04-12T12:49:35-07:00Parsing line-based protocol using Rust and nom part 1<p>Let’s suppose we have some kind of line protocol and we would like to parse it in Rust. Here is an example how this protocol might look like:</p>
<pre><code class="prettyprint lang-C">"#MEAS_NUM;voltage;20.1;V\n"
"#MEAS_TEXT;serial;CAFEBABE\n"
"#INPUT;Is it broken?;YES,NO,MAYBE\n"
</code></pre>
<p>As you can see there are different messages that must be parsed. Let’s create some structs to describe their payloads: </p>
<pre><code class="prettyprint lang-Rust">// "#MEAS_NUM;voltage;20.1;V"
struct Num {
name: String,
value: f32,
unit: String
}
// "#MEAS_TEXT;serial;CAFEBABE"
struct Str {
name: String,
value: String,
}
// "#INPUT;Is it broken?;YES,NO,MAYBE"
struct Input {
message: String,
variants: Vec<String>
}
</code></pre>
<p>We also need a <code class="prettyprint">Message</code> type to combine all these possible messages:</p>
<pre><code class="prettyprint lang-Rust">enum Message {
Number(Num),
Text(Str),
Input(Input),
}
</code></pre>
<p>This allows us to define a parser function that takes in a <code class="prettyprint">line</code> and outputs a <code class="prettyprint">Message</code>:</p>
<pre><code class="prettyprint lang-Rust">fn parser(line: &str) -> Result<Message> {
...
}
</code></pre>
<p>And this function can be used like this:</p>
<pre><code class="prettyprint lang-Rust">for line in input.lines() {
match parser(line) {
Ok(Message::Number(n) => {...}
Ok(Message::Text(t) => {...}
Ok(Message::Input(i) => {...}
Err(e) => println!("parser error: {:?}", e);
}
}
</code></pre>
<p>Nice and Rusty.</p>
<h2 id="parsing_2">Parsing <a class="head_anchor" href="#parsing_2">#</a>
</h2>
<p>Lets go back to actual parsing. Notice that all fields are separated by common delimiter <code class="prettyprint">;</code> therefore we can use <a href="https://doc.rust-lang.org/std/primitive.str.html#method.split">split</a> for segmentation.</p>
<pre><code class="prettyprint lang-Rust">let line = "#MEAS_NUM;voltage;20.1;V";
let v = line.split(";").collect::<Vec<&str>>();
println!("{:?}", v);
> ["#MEAS_NUM", "voltage", "20.1", "V"]
</code></pre>
<p>Excellent, everything is now separated. So lets make an actual parser function based on that idea. Firstly some error handling is needed. For making life easier, <a href="https://github.com/dtolnay/anyhow">anyhow</a> error handling crate is used. We also need to convert from <code class="prettyprint">str</code> to <code class="prettyprint">f32</code>. Therefore we need these 2 imports:</p>
<pre><code class="prettyprint lang-Rust">use std::str::FromStr;
use anyhow::{Result, anyhow};
</code></pre>
<pre><code class="prettyprint">fn parse(line: &str) -> Result<Message> {
let mut token = line.split(";");
let msg = match token.next() {
Some(m) if m == "#MEAS_NUM" => {
let name = token
.next()
.ok_or(anyhow!("no name"))
.map(|s| s.to_string())?;
let value = token
.next()
.ok_or(anyhow!("no value"))
.and_then(|s| Ok(f32::from_str(s)))??;
let unit = token
.next()
.ok_or(anyhow!("no unit"))
.map(|s| s.to_string())?;
Ok(Message::Number(Num {name, value, unit}))
}
Some(m) if m == "#MEAS_TEXT" => {
let name = token
.next()
.ok_or(anyhow!("no name"))
.map(|s| s.to_string())?;
let value = token
.next()
.ok_or(anyhow!("no value"))
.map(|s| s.to_string())?;
Ok(Message::Text(Str {name, value}))
}
Some(m) if m == "#INPUT" => {
let message = token
.next()
.ok_or(anyhow!("no message"))
.map(|s| s.to_string())?;
let variants = token
.next()
.ok_or(anyhow!("no variants"))
.map(|s| s.split(",")
.map(|v| v.to_string()
)
.collect::<Vec<_>>())?;
Ok(Message::Input(Input {message, variants}))
}
Some(m) => {
Err(anyhow!("incorrect header {:}", m))
}
None => {
Err(anyhow!("incorrect input {:}", line))
}
};
msg
}
</code></pre>
<p>Parsing begins by splitting line into tokens and after that it is checked whether first token represents some header like <code class="prettyprint">#MEAS_NUM</code> or <code class="prettyprint">#MEAS_TEXT</code> from protocol.<br>
Lets suppose it was <code class="prettyprint">#MEAS_NUM</code>. Next it must be checked if <code class="prettyprint">name</code> field is also there. </p>
<pre><code class="prettyprint">let name = token
.next()
.ok_or(anyhow!("no name"))
.map(|s| s.to_string())?;
</code></pre>
<p>It could be that <code class="prettyprint">.next()</code> produces <code class="prettyprint">None</code> meaning that there isn’t any data here. Parser should report this as an error because we are unable to parse any further. Conveniently <code class="prettyprint">.ok_or</code> can be used to convert from <code class="prettyprint">Option</code> type to <code class="prettyprint">Result</code> type and <code class="prettyprint">anyhow!("no name")</code> creates an <code class="prettyprint">Error</code> with a custom message. We are using <code class="prettyprint">String</code> as data type so <code class="prettyprint">.map(|s| s.to_string())</code> does conversion from <code class="prettyprint">&str</code> to <code class="prettyprint">String</code>. Notice that <code class="prettyprint">?</code> is used in the end. It unwraps a value if everything was successful or returns <code class="prettyprint">Error</code> if something failed.</p>
<p>Parsing <code class="prettyprint">value: f32</code> is also interesting:</p>
<pre><code class="prettyprint">let value = token
.next()
.ok_or(anyhow!("no value"))
.and_then(|s| Ok(f32::from_str(s)))??;
</code></pre>
<p>Here we also use <code class="prettyprint">and_then</code> combinator which runs a function if previous result was successful. So it runs only if <code class="prettyprint">.next()</code> has returned at least something. Return value from <code class="prettyprint">f32::from_str(s)</code> is wrapped to <code class="prettyprint">Ok()</code> because without that <code class="prettyprint">Error</code> types do not match. Notice that Rust compiler gives us nice hint here:</p>
<pre><code class="prettyprint">40 | .and_then(|s| f32::from_str(s))?;
| ^^^^^^^^^^^^^^^^
| |
| expected struct `anyhow::Error`, found struct `std::num::ParseFloatError`
| help: try using a variant of the expected enum: `Ok(f32::from_str(s))`
|
</code></pre>
<p>Finally double <code class="prettyprint">??</code> is used to unwrap actual value from <code class="prettyprint">Result<Result<f32>></code> or stop parsing and return with an error.</p>
<h2 id="tests_2">Tests <a class="head_anchor" href="#tests_2">#</a>
</h2>
<p>All right, lets try it out with correct inputs first.</p>
<pre><code class="prettyprint lang-Rust">let s = "#MEAS_NUM;voltage;20.1;V";
println!("parsed: {:?}", parse(s));
let s = "#MEAS_TEXT;serial;CAFEBABE";
println!("parsed: {:?}", parse(s));
let s = "#INPUT;Is it broken?;YES,NO,MAYBE";
println!("parsed: {:?}", parse(s));
> parsed: Ok(Number(Num { name: "voltage", value: 20.1, unit: "V" }))
> parsed: Ok(Text(Str { name: "serial", value: "CAFEBABE" }))
> parsed: Ok(Input(Input { message: "Is it broken?", variants: ["YES", "NO", "MAYBE"] }))
</code></pre>
<p>Very good everything is parsed as expected. And now with incorrect input:</p>
<pre><code class="prettyprint lang-Rust">let s = "#MEAS_NUM;voltage;20.1";
println!("parsed: {:?}", parse(s));
let s = "#MEAS_NUM;voltage;twenty;V";
println!("parsed: {:?}", parse(s));
> parsed: Err(no unit)
> parsed: Err(invalid float literal)
</code></pre>
<p>As expected errors are returned. However error messages itself are not that good.</p>
<p>What next? Could it be possible to improve such a parser. There is already quite a bit of manual error handling in current version. Maybe somebody has already figured out how to do it in a clean manner and with better error messages?</p>
<p>You betcha! Enter <a href="https://github.com/Geal/nom">nom</a> - Rust parser combinator framework, which I am going to use in part 2.</p>
tag:andres.svbtle.com,2014:Post/convert-subprocess-stdout-stream-into-non-blocking-iterator-in-rust2015-07-24T04:35:53-07:002015-07-24T04:35:53-07:00Convert subprocess stdout stream into non-blocking iterator in Rust<p>In one of my programs I had to interact with another subprocess. This subprocess took data from stdin and wrote result to stdout. It wasn’t just simple reading and writing - it took constant data stream from stdin and somewhere in the middle writes something to stdout. I had to capture this something as soon as possible and take actions according this response.</p>
<p>The main would look like this: </p>
<pre><code class="prettyprint"> fn main() {
let process = Process::new();
loop {
let data = get_some_data();
process.push(data);
for response in proc.responses() {
// do something based on response
}
}
}
</code></pre>
<h1 id="blocking-iterator_1">Blocking iterator <a class="head_anchor" href="#blocking-iterator_1">#</a>
</h1>
<p>Firstly I just tried to read lines from process stdout. However I discovered that this <code class="prettyprint">BufReader</code> is blocking. Code for the blocking stdout iterator is below. Because it is blocking we cannot put it into loop. Therefore I had to come up with a non-blocking version.</p>
<pre><code class="prettyprint lang-Rust">
use std::io::prelude::*;
use std::process::{Command, Stdio};
use std::io::BufReader;
pub struct Process {
process: std::process::Child,
}
impl Process {
pub fn new() -> Process {
let mut process =
Command::new("someprocess.sh")
.stdin(Stdio::piped())
.stdout(Stdio::inherit())
.spawn().unwrap();
Process {process: process}
}
pub fn push(&mut self, buf: &[u8]) {
let mut stdin = self.process.stdin
.as_mut().unwrap();
stdin.write_all(buf);
}
pub fn responses(&mut self) -> ProcessIntoIterator {
ProcessIntoIterator {
subprocess: self,
}
}
}
pub struct ProcessIntoIterator<'a> {
subprocess: &'a mut Process,
}
impl <'a>Iterator for ProcessIntoIterator<'a> {
type Item = String;
fn next(&mut self) -> Option<String> {
let stdout = self.subprocess.process.stdout
.as_mut().unwrap();
let reader = BufReader::new(stdout);
let mut result: Option<String> = None;
// blocks until subrocess finishes
for line in reader.lines() {
result = Some(line.unwrap());
}
if result.is_some() {
result
}
else {
None
}
}
}
</code></pre>
<h1 id="nonblocking-iterator_1">Non-blocking iterator <a class="head_anchor" href="#nonblocking-iterator_1">#</a>
</h1>
<p>This version has method called <code class="prettyprint">run()</code> which starts a thread that reads data from stdout and forwards it to iterator if data is available. Notice that <a href="http://doc.rust-lang.org/std/sync/mpsc/fn.channel.html">channel</a> is used for sending data from stdout reader thread to iterator (main) thread. If data is not available iterator returns None and quits. Exactly what I needed.</p>
<pre><code class="prettyprint lang-Rust">
use std::io::prelude::*;
use std::process::{Command, Stdio};
use std::sync::mpsc;
use std::thread;
use std::io::BufReader;
pub struct Process {
process: std::process::Child,
tx: mpsc::Sender<Option<String>>,
rx: mpsc::Receiver<Option<String>>,
}
impl Process {
pub fn new() -> Process {
let mut process =
Command::new("someprocess.sh")
.stdin(Stdio::piped())
.stdout(Stdio::inherit())
.spawn().unwrap();
let (tx, rx) = mpsc::channel();
Process {process: process,
tx: tx,
rx: rx,
}
}
pub fn run(&mut self) {
let tx = self.tx.clone();
let stdout = self.process.stdout
.take().unwrap();
thread::spawn(move || {
let reader = BufReader::new(stdout);
for line in reader.lines() {
tx.send(Some(line.unwrap()));
}
});
}
pub fn push(&mut self, buf: &[u8]) {
let mut stdin = self.process.stdin
.as_mut().unwrap();
stdin.write_all(buf);
}
pub fn packets(&mut self) -> ProcessIntoIterator {
ProcessIntoIterator {
subprocess: self,
}
}
}
pub struct ProcessIntoIterator<'a> {
subprocess: &'a mut Process,
}
impl <'a>Iterator for ProcessIntoIterator<'a> {
type Item = String;
fn next(&mut self) -> Option<String> {
let data = self.subprocess.rx.try_recv();
if data.is_ok() {
data.unwrap()
}
else {
None
}
}
}
</code></pre>
<p>Thats it! It works as expected. With non-blocking version we also have to call <code class="prettyprint">process.run()</code> to start the thread:</p>
<pre><code class="prettyprint"> fn main() {
let process = Process::new();
process.run();
loop {
let data = get_some_data();
process.push(data);
for response in proc.responses() {
// do something based on response
}
}
}
</code></pre>
tag:andres.svbtle.com,2014:Post/examples-of-creating-rust-bindings2015-07-09T05:22:22-07:002015-07-09T05:22:22-07:00Examples of creating Rust bindings<p>Couple of months ago I developed two SDR tools called <a href="http://andres.svbtle.com/doppler-correction-tool-for-sdr">doppler</a> and <a href="http://andres.svbtle.com/liquiddsp-based-command-line-fm-demodulator">demod</a>. These tools were using unix pipe to transfer IQ data stream from one process to another and they were written in C that time.</p>
<p>I have read a lot of blog posts about <a href="http://www.rust-lang.org">Rust</a> from the beginning of the year 2015 and I decided to give it a try. The best way to learn a new language is to use it, therefore I decided to rewrite those tools in Rust. This post mostly shows how to use <a href="https://doc.rust-lang.org/book/ffi.html">Foreign Function interface - FFI</a> to create Rust bindings.</p>
<h1 id="doppler_1">Doppler <a class="head_anchor" href="#doppler_1">#</a>
</h1>
<p>Firstly <a href="https://github.com/cubehub/libgpredict">libgpredict</a> was improved to use cmake for building and installing proper library. It is much easier to deal with Rust FFI if library is used instead of just bunch of C files. For example C version of <code class="prettyprint">doppler</code> statically linked needed files from git submodule, which was fine for C version.</p>
<p>I could not use <a href="https://github.com/crabtw/rust-bindgen">rust-bindgen</a> for generating Rust functions from C because it did not work at this time. I was using Rust 1.0 beta and rust-bindgen worked with nightly or did not work at all. Therefore I had to do bindings manually. Here are some examples.</p>
<p><strong>C</strong></p>
<pre><code class="prettyprint">typedef struct {
char* name;
char* nickname;
char* website;
tle_t tle; /*!< Keplerian elements */
int flags; /*!< Flags for algo ctrl */
sgpsdp_static_t sgps;
...
} sat_t;
</code></pre>
<p><strong>Rust</strong></p>
<pre><code class="prettyprint">#[repr(C)]
pub struct sat_t {
pub name: *const c_char,
pub nickname: *const c_char,
pub website: *const c_char,
pub tle: tle_t, // Keplerian elements
pub flags: c_int, // Flags for algo ctrl
pub sgps: sgpsdp_static_t,
...
}
</code></pre>
<p>This struct is rather straight forward and as you can see Rust <code class="prettyprint">libc</code> provides all necessary C types (<code class="prettyprint">*const c_char</code> etc).</p>
<h2 id="dealing-with-raw-pointers_2">Dealing with raw pointers <a class="head_anchor" href="#dealing-with-raw-pointers_2">#</a>
</h2>
<p>Lets see how function bindings are made. Here is example from function that parses TLE files:</p>
<p><strong>C</strong></p>
<pre><code class="prettyprint">int Get_Next_Tle_Set( char lines[3][80], tle_t *tle );
</code></pre>
<p><strong>Rust</strong></p>
<pre><code class="prettyprint">#[link(name = "gpredict")]
extern {
pub fn Get_Next_Tle_Set(line: *const c_char, tle: *mut tle_t) -> c_int;
}
</code></pre>
<p>Notice how char array and <code class="prettyprint">tle</code> reference is translated to Rust. It was quite easy to translate from C function to Rust however it was rather difficult to use this function compared to C version.</p>
<pre><code class="prettyprint">use std::ffi::{CString};
use libc::{c_char};
use std::{cmp, ptr};
fn copy_memory(src: &[u8], dst: &mut [u8]) -> usize {
let len = cmp::min(src.len(), dst.len());
unsafe {
ptr::copy_nonoverlapping(&src[0], &mut dst[0], len);
}
len
}
pub fn create_tle_t(tle: Tle) -> ffipredict::tle_t {
let mut tle_t = ffipredict::tle_t {
epoch: 0.0,
epoch_year: 0,
// some init details missing
};
let name = CString::new(tle.name).unwrap();
let line1 = CString::new(tle.line1).unwrap();
let line2 = CString::new(tle.line2).unwrap();
let mut buf = [[0u8; 80]; 3];
copy_memory(name.as_bytes_with_nul(), &mut buf[0]);
copy_memory(line1.as_bytes_with_nul(), &mut buf[1]);
copy_memory(line2.as_bytes_with_nul(), &mut buf[2]);
unsafe { ffipredict::Get_Next_Tle_Set(transmute::<&u8, *const c_char>(&buf[0][0]), &mut tle_t)};
tle_t
}
</code></pre>
<p>Notice how <a href="http://doc.rust-lang.org/std/mem/fn.transmute.html">transmute</a> is used to cast Rust array to raw pointer.</p>
<h1 id="demod_1">Demod <a class="head_anchor" href="#demod_1">#</a>
</h1>
<p>Porting <a href="https://github.com/cubehub/demod">demod</a> to Rust was much easier, because it was possible to use <a href="https://github.com/crabtw/rust-bindgen">rust-bindgen</a> this time. Actually later I took out <a href="https://github.com/jgaeddert/liquid-dsp">liquid-dsp</a> parts from demod and made a separate wrapper library called <a href="https://github.com/cubehub/rust-liquid-dsp">rust-liquid-dsp</a>.</p>
<p>NB: to build <code class="prettyprint">rust-bindgen</code> following command must be executed on Mac OS X before building it:</p>
<pre><code class="prettyprint">echo export DYLD_LIBRARY_PATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/:$DYLD_LIBRARY_PATH >> ~/.profile
</code></pre>
<p>Then just use <code class="prettyprint">cargo</code> to build <code class="prettyprint">rust-bindgen</code>:</p>
<pre><code class="prettyprint">cargo build
</code></pre>
<p>Rust does not have native support for complex numbers. Therefore <a href="https://github.com/jgaeddert/liquid-dsp/blob/master/include/liquid.h">liquid.h</a> must be little bit changed to create custom struct for complex numbers.</p>
<p>Original snippet from <code class="prettyprint">liquid.h</code> file:</p>
<pre><code class="prettyprint">#if LIQUID_USE_COMPLEX_H==1
# include <complex.h>
# define LIQUID_DEFINE_COMPLEX(R,C) typedef R _Complex C
#elif defined _GLIBCXX_COMPLEX || defined _LIBCPP_COMPLEX
# define LIQUID_DEFINE_COMPLEX(R,C) typedef std::complex<R> C
#else
# define LIQUID_DEFINE_COMPLEX(R,C) typedef struct {R real; R imag;} C;
#endif
</code></pre>
<p>Remove those lines except this one:</p>
<pre><code class="prettyprint">#define LIQUID_DEFINE_COMPLEX(R,C) typedef struct {R real; R imag;} C;
</code></pre>
<p>Now the following command generates Rust bindings file <code class="prettyprint">ffiliquid.rs</code> from liquid-dsp C files:</p>
<pre><code class="prettyprint">./target/debug/bindgen -l liquid -match liquid.h -o ~/Development/rust-liquid-dsp/src/ffiliquid.rs ~/Downloads/liquid-dsp/include/liquid.h
</code></pre>
<p>Here is an example how <code class="prettyprint">liquid-dsp</code> <code class="prettyprint">fredem</code> object is wrapped to Rust.</p>
<p><a href="https://github.com/cubehub/rust-liquid-dsp/blob/master/src/freqdem.rs">freqdem.rs</a></p>
<pre><code class="prettyprint">use ffiliquid;
use super::{Complex32};
pub struct Freqdem {
object: ffiliquid::freqdem,
}
impl Freqdem {
/// create freqdem object (frequency demodulator)
/// _kf : modulation factor
pub fn new(_kf: f32) -> Freqdem {
let demod: ffiliquid::freqdem = unsafe{ffiliquid::freqdem_create(_kf)};
Freqdem{object: demod}
}
/// demodulate sample
/// _r : received signal r(t)
/// _m : output message signal m(t)
pub fn demodulate(&self, _r: Complex32, _m: *mut f32) {
unsafe{ffiliquid::freqdem_demodulate(self.object, _r, _m)};
}
/// demodulate block of samples
/// _r : received signal r(t) [size: _n x 1]
/// _n : number of input, output samples
/// _m : message signal m(t), [size: _n x 1]
pub fn demodulate_block(&self, _r: &mut [Complex32], _n: u32, _m: &mut [f32]) {
unsafe{ffiliquid::freqdem_demodulate_block(self.object, _r.as_mut_ptr(), _n, _m.as_mut_ptr())};
}
}
impl Drop for Freqdem {
fn drop(&mut self) {
unsafe{ffiliquid::freqdem_destroy(self.object)};
}
}
</code></pre>
tag:andres.svbtle.com,2014:Post/doppler-correction-tool-for-sdr2015-02-24T07:12:48-08:002015-02-24T07:12:48-08:00Doppler correction tool for SDR<p>Doppler is the last tool that is missing for receiving data from satellites using software defined radio (SDR) in a UNIX fashion:</p>
<blockquote>
<p>Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.</p>
<p>– Doug McIlroy</p>
</blockquote>
<p>As I mentioned in my <a href="http://andres.svbtle.com/pipe-sdr-iq-data-through-fm-demodulator-for-fsk9600-ax25-reception">earlier post</a> there exists many GUI based SDR tools for doing doppler correction and demodulation. However there aren’t tools available for doing this:</p>
<pre><code class="prettyprint">rtl_sdr | doppler | demod | multimon-ng
</code></pre>
<p>Some readers might already know that <a href="http://sdr.osmocom.org/trac/wiki/rtl-sdr">rtl_sdr</a> is used for getting IQ data out of simple SDR dongles and <a href="https://github.com/EliasOenal/multimon-ng">multimon-ng</a> is a tool that decodes actual packets from demodulated audio. <a href="http://andres.svbtle.com/liquiddsp-based-command-line-fm-demodulator">Demod</a> is a new tool that I wrote for demodulating IQ stream. Currently it supports only FM modulation, however it is quite easy to add more demodulators that are supported by <a href="https://github.com/jgaeddert/liquid-dsp">liquid-dsp library</a>.<br>
<a href="https://github.com/cubehub/doppler">Doppler</a> was the last missing piece from the pipeline.</p>
<h1 id="doppler_1">Doppler <a class="head_anchor" href="#doppler_1">#</a>
</h1>
<p><a href="https://github.com/cubehub/doppler">Doppler</a> uses the same library for calculating satellite position and other parameters as <a href="http://gpredict.oz9aec.net">Gpredict</a>. Bill Shupp has extracted core functions from Gpredict source and released them as standalone library called <a href="https://github.com/shupp/libgpredict">libgpredict</a>. This is the library that does heavy lifting for calculating doppler shift.<br>
Baseband signal is shifted by doppler frequency in the following way:</p>
<pre><code class="prettyprint">float complex c_sample;
float complex c_corrector;
int n = 0;
for (k=0; k<len; k+=2) {
// convert int16_t IQ to complex float
c_sample = iqbuffer[k] / 32768.0 +
iqbuffer[k+1] / 32768.0 * I;
c_corrector = cexpf(0.0 -2*M_PI*doppler_hz/samplerate*n*I);
c_sample = c_sample * c_corrector;
// convert float back to int16_t IQ
iqbuffer[k] = crealf(c_sample) * 32767.0; // I part
iqbuffer[k+1] = cimagf(c_sample) * 32767.0; // Q part
n++;
}
</code></pre>
<p>Thats it - basically this is the only thing what it does. Computes doppler shift and centers baseband signal according to this value.</p>
<h1 id="examples_1">Examples <a class="head_anchor" href="#examples_1">#</a>
</h1><h2 id="working-in-realtime_2">Working in realtime <a class="head_anchor" href="#working-in-realtime_2">#</a>
</h2>
<p>Suppose you have RTL-SDR dongle and you would like to decode AX.25 packets from cube satellite ESTCube-1 that transmits last days on 437.505 MHz. Lets set <code class="prettyprint">rtl_sdr</code> center frequency off by 5 kHz to mitigate DC offset that is occurring with some of the dongles. That is why here <code class="prettyprint">rtl_sdr</code> uses 437.500 MHz as center frequency and <code class="prettyprint">doppler</code> uses <code class="prettyprint">-o 5000</code> (offset) parameter. Here <a href="https://github.com/cubehub/multimon-ng">multimon-ng</a> fork that supports 48 kHz input is used because it decodes more reliably than original 22.050 kHz. TLE file cubesat.txt can be downloaded from <a href="https://celestrak.com/NORAD/elements/cubesat.txt">here</a>.</p>
<pre><code class="prettyprint">rtl_sdr -f 437500000 -s 1024000 -g 20 - | doppler -s 1024000 -i i16 -d -t cubesat.txt -n 'ESTCUBE 1' --location lat=58.26541,lon=26.46667,alt=76.1 -f 437505000 -o 5000 | demod -s 1024000 -r 48000 -b 4500 -m fm d=3500 | multimon-ng -t raw -a FSK9600 /dev/stdin
</code></pre>
<h2 id="working-with-recordings_2">Working with recordings <a class="head_anchor" href="#working-with-recordings_2">#</a>
</h2>
<p>Suppose we have recorded a satellite overpass to a file and now we want to decode packets from that file. Here <code class="prettyprint">--log doppler.log</code> is used to write doppler information to the file instead of stderr. Also offset parameter <code class="prettyprint">-o 5000</code> is used because recording was taken with center frequency of 437.500 MHz. If dealing with old recordings use TLE parameters from the same time, otherwise doppler correction might be wrong.</p>
<pre><code class="prettyprint">cat ec1_256000_i16_2015-02-19T20-53-56Z.iq | doppler -s 256000 -i i16 -d -t cubesat.txt -n 'ESTCUBE 1' --location lat=58.26541,lon=26.46667,alt=76.1 -f 437505000 -o 5000 --time 2015-02-19T20:53:56 --log doppler.log | demod -s 256000 -r 48000 -b 4500 -m fm d=3500 | multimon-ng -t raw -a FSK9600 /dev/stdin
</code></pre>
<h1 id="disclaimer_1">Disclaimer <a class="head_anchor" href="#disclaimer_1">#</a>
</h1>
<p>I have not yet used it in real environment, so there might be some quirks. Doppler correction was also little bit off when I tried it with old recordings. It was especially off in the end of a the overpass. I am planning to add automatic frequency correction (AFC) support to this tool to correct those mismatches. It should improve packet decoding quite a lot because it is very important to feed demodulator with zero offset baseband signal.</p>
tag:andres.svbtle.com,2014:Post/liquiddsp-based-command-line-fm-demodulator2015-02-23T11:43:45-08:002015-02-23T11:43:45-08:00Liquid-dsp based command line FM demodulator<p>In my last post I wrote about how I hacked <a href="https://github.com/andresv/demod">Google radioreceiver</a> into command line based FM demodulator that can be used to pipe demodulated audio into multimon-ng for AX.25 packet decoding. However it had some significant flaws. For example as far as I know it only worked reliably if input sample rate was 1024 kHz and output 48 kHz. I did not look too much into problem because I wanted to rewrite it anyway to make it more universal for supporting more modulations beside FM and AM.</p>
<p>I am follower of <a href="http://www.rtl-sdr.com">www.rtl-sdr.com</a> and recently there was a <a href="http://www.rtl-sdr.com/cubicsdr-new-open-source-cross-platform-sdr-software/">post</a> about new SDR software called <a href="https://github.com/cjcliffe/CubicSDR">CubicSDR</a>. What makes it interesting for me is that it uses library called <a href="http://liquidsdr.org">liquid-dsp</a> for doing signal processing. I tried to find decent signal processing library that is intended for SDR, but I ended up forking Google radioreceiver out of frustation. I did not have any luck finding such library until I stumbled upon <a href="https://github.com/jgaeddert/liquid-dsp">liquid-dsp</a>.</p>
<p>Liquid-dsp is written in pure C and it does not have any dependencies which make it very suitable for embedded platforms. GNU Radio is very, very bloated compared to this. I am going to use it on BeagleBoneBlack or even better on the new 4 core RaspberryPi, therefore liquid-dsp embedded oriented nature fits very well for me.</p>
<h1 id="fm-demodulation_1">FM demodulation <a class="head_anchor" href="#fm-demodulation_1">#</a>
</h1>
<p>Here is hypothetical demodulation loop where block of complex baseband signal is read from “source” and demodulated output is written to stdout so it can be piped to another program. Notice how to setup modulation factor <code class="prettyprint">kf</code> for FSK demodulation with deviation of 3500 Hz.</p>
<pre><code class="prettyprint">#define SAMPLERATE 1024000.0f
#define FSK_DEVIATION_HZ 3500.0f
#define NUM_SAMPLES 8192
float kf = FSK_DEVIATION_HZ/SAMPLERATE; // modulation factor
float complex r[NUM_SAMPLES]; // received signal
float y[NUM_SAMPLES]; // demodulator output
freqmod mod = freqmod_create(kf);
while (1) {
read_complex_iq_block(r, NUM_SAMPLES); // made up function
freqdem_demodulate_block(dem, r, NUM_SAMPLES, y);
fwrite((uint8_t*)y, 4, NUM_SAMPLES, stdout);
}
freqmod_destroy(mod);
</code></pre>
<h1 id="lowpass-filtering_1">Lowpass filtering <a class="head_anchor" href="#lowpass-filtering_1">#</a>
</h1>
<p>Demodulation loop has a little problem. Its input baseband signal sample rate is 1024 kHz, however we would like to demodulate FSK modulated signal which has deviation of ±3.5 kHz and data rate of 9.6 kbps. The problem is that currently unfiltered signal goes to demodulator, therefore it is unable to figure out our narrow band signal correctly, because of high frequency noise which is present in baseband signal. Lowpass filter must be added to cut off out of band high frequency signals. </p>
<pre><code class="prettyprint">#define SAMPLERATE 1024000.0f
#define CUTOFF_HZ 4200.0f
#define NUM_SAMPLES 8192
float fc = CUTOFF_HZ/SAMPLERATE; // cutoff frequency
unsigned int h_len = 64; // filter length
float As = 70.0f; // stop-band attenuation
float complex r[NUM_SAMPLES]; // received signal
float complex rf[NUM_SAMPLES]; // filtered signal
// design filter from prototype and scale to bandwidth
firfilt_crcf q = firfilt_crcf_create_kaiser(h_len, fc, As, 0.0f);
firfilt_crcf_set_scale(q, 2.0f*fc);
while (1) {
read_complex_iq_block(r, NUM_SAMPLES); // made up function
for (int i=0; i<NUM_SAMPLES; i++)
firfilt_crcf_push(q, r[i]);
firfilt_crcf_execute(q, &rf[i]);
}
// here lowpass filtered signal "rf" can be given to demodulator as shown above
}
firfilt_crcf_destroy(q);
</code></pre>
<p>I have not yet studied how this filter response actually looks like. I just used it with cube satellite ESTCube-1 recording and those filter parameters worked very well. Notice that here <code class="prettyprint">fc</code> defines filter cutoff frequency centered around 0 Hz. In this example it is ±4.2 kHz.<br>
Here is screenshot from Baudline showing AX.25 packets after filtering step:<br>
<a href="https://svbtleusercontent.com/vvs4h8ug3oxbfg.jpg"><img src="https://svbtleusercontent.com/vvs4h8ug3oxbfg_small.jpg" alt="filtered.jpg"></a></p>
<h1 id="resampling_1">Resampling <a class="head_anchor" href="#resampling_1">#</a>
</h1>
<p>So far so good, filtering and demodulation works. Now only resampling is missing which was the main problem with Google radioreceiver. Fortunately arbitrary resampling is supported in liquid-dsp. Here is example how to use it:</p>
<pre><code class="prettyprint">#define IN_SAMPLERATE 1024000.0f
#define OUT_SAMPLERATE 48000.0f
#define NUM_SAMPLES 8192
float r= OUT_SAMPLERATE/IN_SAMPLERATE;
float As=60.0f; // resampling filter stop-band attenuation [dB]
unsigned int n= NUM_SAMPLES; // number of input samples
msresamp_crcf q = msresamp_crcf_create(r,As);
msresamp_crcf_print(q);
float delay = msresamp_crcf_get_delay(q);
// number of input samples (zero-padded)
unsigned int nx = n + (int)ceilf(delay) + 10;
// output buffer with extra padding for good measure
unsigned int ny_alloc = (unsigned int) (2*(float)nx * r); // allocation for output
float complex r[nx]; // received signal
float complex y[ny_alloc]; // resampled signal
while (1) {
unsigned int ny;
read_complex_iq_block(r, NUM_SAMPLES); // made up function
msresamp_crcf_execute(q, r, NUM_SAMPLES, y, &ny);
// here "y" is signal with sample rate of OUT_SAMPLERATE and "ny" shows how many samples are in "y"
}
msresamp_crcf_destroy(q);
</code></pre>
<p><a href="https://github.com/jgaeddert/liquid-dsp/blob/master/examples/msresamp_crcf_example.c">Original example</a> uses <code class="prettyprint">nx</code> in <code class="prettyprint">msresamp_crcf_execute</code>, however it did not work for me. I got it to work with <code class="prettyprint">NUM_SAMPLES</code>.</p>
<pre><code class="prettyprint">msresamp_crcf_execute(q, r, nx, y, &ny);
</code></pre>
<p>vs</p>
<pre><code class="prettyprint">msresamp_crcf_execute(q, r, NUM_SAMPLES, y, &ny);
</code></pre>
<p>Here is output resampled from 126 kHz to 48 kHz:<br>
<a href="https://svbtleusercontent.com/m0bgtnjbeovzww.jpg"><img src="https://svbtleusercontent.com/m0bgtnjbeovzww_small.jpg" alt="resampled.jpg"></a></p>
<h1 id="putting-it-all-together_1">Putting it all together <a class="head_anchor" href="#putting-it-all-together_1">#</a>
</h1>
<p>Final tool called demod can be downloaded from <a href="https://github.com/cubehub/demod">github</a>.</p>
<p>Here <code class="prettyprint">sdr_fsk9600.wav</code> is recording that contains some AX.25 packets. Those packets can be demodulated and decoded using following command:</p>
<pre><code class="prettyprint">sox -t wav sdr_fsk9600.wav -esigned-integer -b16 -r 126000 -t raw - | demod -s 126000 -r 48000 -b 4200 -m fm d=3500 | multimon-ng -t raw -a FSK9600 /dev/stdin
</code></pre>
<p>Here <a href="https://github.com/cubehub/multimon-ng">multimon-ng fork</a> that processes 48 kHz input instead of 22.050 kHz is used. It is because multion-ng decodes much more packets with 48 kHz input. Firstly I thought that Google radioreceiver 22.050 kHz output was not good enough for multimon-ng, however now even with liquid-dsp based approach it works better using 48 kHz sampling rate. I should really dig into multimon-ng to see why it works so badly.</p>
tag:andres.svbtle.com,2014:Post/pipe-sdr-iq-data-through-fm-demodulator-for-fsk9600-ax25-reception2015-01-06T13:43:15-08:002015-01-06T13:43:15-08:00Pipe SDR IQ data through FM demodulator for FSK9600 AX25 reception<h1 id="problem_1">Problem <a class="head_anchor" href="#problem_1">#</a>
</h1>
<p>I thought that RTL-SDR and its command line tools are so common in these days that software for decoding everything and especially simple FSK9600 definitely exists. I was kind of right…except there are some corner cases. I looked for command line tools because decoding should work on BeagleBoneBlack which is not the most powerful computer.</p>
<p>There is <a href="https://github.com/keenerd/rtl-sdr">rtl_fm</a> that uses RTL-SDR dongle to receive actual signal from air and it also demodulates signal on the fly. Demodulated audio signal can then be piped to <a href="https://github.com/EliasOenal/multimon-ng">multimon-ng</a> that decodes FSK9600 AX25 packets.</p>
<pre><code class="prettyprint">rtl_fm -f 437.505M -M fm -s 1024000 -r 22050 | multimon-ng -t raw -a FSK9600 /dev/stdin
</code></pre>
<p>Actually I have not tested this command in real situation, but it should work with strong signals. Narrow band FM uses +- 2.5 kHz deviation, but some nano satellites are using deviation around +- 3.5 kHz, therefore rtl_fm’s demodulation process might not give you the best output.</p>
<p>Here is my corner case problem - what if I want to receive data from satellites? In this case I have to deal with doppler shift. Above mentioned command does not work anymore because center frequency will be too much off from actual frequency. There is no doppler correction support in rtl_fm and this is okay. Unix command line programs should do only one thing and they better do it well.</p>
<h1 id="gqrx-rtltcp_1">Gqrx + rtl_tcp <a class="head_anchor" href="#gqrx-rtltcp_1">#</a>
</h1>
<p>I considered it impossible to receive satellites using only rtl_fm + multimon-ng so I looked for alternatives. What if I just use BeagleBoneBlack as SDR server and heavy lifting is done using Mac or Linux? In that way I should have more chance with doppler correction because decent SDR software typically has plugins for that job. SDR server can be started as:</p>
<pre><code class="prettyprint">rtl_tcp -a 192.168.2.106
</code></pre>
<p><a href="http://gqrx.dk">Gqrx</a> which is a nice graphical SDR waterfall program for Mac and Linux has support for such server. Or at least it seems it has. Maybe there are newer versions that work. However I followed installation instructions from <a href="https://github.com/andresv/homebrew-gnuradio">here</a> and these Gqrx and rtl_tcp versions did not work together.<br>
Sometimes rtl_tcp gave socket error, sometimes Gqrx showed random signals, sometimes it showed even correct signal, but there was huge delay displaying it. I used HackRF to generate continuous signal. Doing this I knew what kind of signal to expect on Gqrx waterfall. Anyway I was too frustrated to continue with this path and I did not even think about possible version mismatch at this time.</p>
<h1 id="pipeable-fm-demodulator_1">Pipeable FM demodulator <a class="head_anchor" href="#pipeable-fm-demodulator_1">#</a>
</h1>
<p>Back to basics. I could not get Gqrx running using rtl_tcp as a data source and actually I did not try too much either. My ultimate goal was to use only command line tools to do the job. I figured out that rtl_fm can also output raw signed 16 bit IQ data. I like rtl_fm more compared to rtl_sdr because rtl_fm by default tunes little bit off the center frequency to reject DC signal. So the goal was such chain:</p>
<pre><code class="prettyprint">rtl_fm -f 437.505M -M raw -s 1024000 -r 22050 | doppler -tle "ESTCUBE 1" cubesat.txt | demod -M fm -deviation 3.5k | multimon-ng -t raw -a FSK9600 /dev/stdin
</code></pre>
<p>I have some doppler corrected raw files already available therefore I left doppler (which is not yet ready) aside. I was interested in if there is pipeable FM demodulator available that can be used as shown an hypothetical example command. After some googling I came to conclusion that radio amateurs are mostly using GUI based SDR programs that are made for MS Windows. It seems that very few people are experimenting with satellite reception using Linux command line tools. Anyway I did not find drop in solution to my problem, however I found couple of open source FM demodulators that can be modified to match my needs. There is <a href="https://github.com/dswiston/pyFmRadio/blob/master/pyFmRadio.py">pyFmRadio</a>, <a href="https://github.com/jorisvr/SoftFM/blob/master/pyfm.py">SoftFM</a> and <a href="https://github.com/google/radioreceiver/tree/master/nacl-src">Google radioreceiver</a>. I gave Google radioreceiver a go because it has nice <a href="https://github.com/google/radioreceiver/blob/master/nacl-src/testing/demod-stdin.cc">testing application</a> already bundled that almost did what I wanted to achieve - take raw IQ data as input and output demodulated audio.</p>
<p>First thing that I noticed was input format, by default it takes 8 bit IQ data as input, however rtl_sdr and rtl_fm are producing signed 16 bit IQ data.</p>
<p>So <code class="prettyprint">samplesFromInt16</code> was added to <strong>dsp.cc</strong> (here is also original <code class="prettyprint">samplesFromUint8</code> for comparison):</p>
<pre><code class="prettyprint">Samples samplesFromUint8(uint8_t* buffer, int length) {
Samples out(length);
for (int i = 0; i < length; ++i) {
out[i] = buffer[i] / 128.0 - 1;
}
return out;
}
Samples samplesFromInt16(int16_t* buffer, int length) {
Samples out(length);
for (int i = 0; i < length; ++i) {
out[i] = buffer[i] / 32768.0;
}
return out;
}
</code></pre>
<p><strong>demod-stdin.cc</strong> was modified to:</p>
<pre><code class="prettyprint">//decoder->decode(samplesFromUint8(reinterpret_cast<uint8_t*>(buffer), read), cfg.stereo);
decoder->decode(samplesFromInt16(reinterpret_cast<int16_t*>(buffer), read / 2), cfg.stereo);
</code></pre>
<p>After these modifications I was able to play raw IQ recording from a FM radio station:</p>
<pre><code class="prettyprint">cat fm_radio_r2.iq | ./demod-stdin -mod WBFM -bandwidth 170000 -maxf 75000 -inrate 230400 -outrate 48000 | play -t raw -r 48k -e signed-integer -b 16 -c 2 -V1 -
</code></pre>
<p>Of course it is possible to listen FM radio using just rtl_fm and play together. My experiment proved that my modifications were correct and FM demodulator really works.</p>
<p>For clarification here is command that was used to capture above mentioned <strong>fm_radio_r2.iq</strong> file:</p>
<pre><code class="prettyprint">rtl_fm -f 103.4M -M raw -s 230400 fm_radio_r2.iq
</code></pre>
<h1 id="fsk9600-data-decoding_1">FSK9600 data decoding <a class="head_anchor" href="#fsk9600-data-decoding_1">#</a>
</h1>
<p>I have already done some GNU Radio block diagrams to do quadrature demodulation for FSK9600 signals. Here is the diagram that outputs 2 files, one of them is demodulated, other one has just gone through low pass filter:<br>
<a href="https://svbtleusercontent.com/li2i5o4gkrb9pw.png"><img src="https://svbtleusercontent.com/li2i5o4gkrb9pw_small.png" alt="gnuradio.png"></a></p>
<p>Notice that original signal is mixed with -49.26 kHz cosine signal source, because original recording is not centered around 0 frequency, which is needed for other GNU Radio blocks.</p>
<p>Following command was used to test if demodulated audio produced by GNU Radio is fine enough for multimon-ng. Indeed it worked nicely.</p>
<pre><code class="prettyprint">sox -t wav 9600AFdump.wav -r 22050 -esigned-integer -b16 -t raw - | multimon-ng -t raw -a FSK9600 /dev/stdin
</code></pre>
<p>Original <strong>demod-stdin</strong> produces 2 channel demodulated audio, however multimon-ng uses 1 channel audio as input, therefore following modification was added to <strong>demod-stdin.cc</strong>:</p>
<pre><code class="prettyprint">//cout.write(outBlock, 4);
cout.write(outBlock, 2);
</code></pre>
<p>It just writes only left channel audio to output. Right channel was the same anyway if narrowband FM demodulator was used.</p>
<p>After that <strong>9600RFdump.wav</strong> was passed through modified <strong>demod-stdin</strong> using narrowband FM demodulation with 3.5 kHz deviation:</p>
<pre><code class="prettyprint">sox -t wav 9600RFdump.wav -esigned-integer -b16 -r 1024000 -t raw - | ./demod-stdin -mod NBFM -maxf 3500 -inrate 1024000 -outrate 22050 | multimon-ng -t raw -a FSK9600 /dev/stdin
</code></pre>
<p>It did not work this time. Comparing GNU radio quadrature demodulated audio with <strong>demod-stdin</strong> output revealed following:</p>
<p><a href="https://svbtleusercontent.com/nbljhyepzjohma.png"><img src="https://svbtleusercontent.com/nbljhyepzjohma_small.png" alt="quadrature vs fm demod.png"></a></p>
<p>Bottom signal is from <strong>demod-stdin</strong> and its output is quite “wobbly” compared to GNU Radio. Little side note - this picture is made using Low Pass Filter 5 kHz cutoff frequency and 10 KHz channel_width instead of 8 KHz and 12.5 KHz that can be seen on block diagram above. Later settings improved SNR a little (preamble peaks were higher, this nice sinusoid is 0xAA preamble pattern), but it was still “wobbling”. Looking at the picture gave me an idea to make this output rectangular as can be seen on quadrature case. It was very easy to implement, only following 2 lines were added to <strong>demod-stdin.cc</strong>:</p>
<pre><code class="prettyprint"> if (left > 0) left = 32767;
if (left < 0) left = -32767;
</code></pre>
<p>For some reason multimon-ng was still unable to decode data after these modifications. To see what is difference between GNU Radio and <strong>demod-stdin</strong> demodulated audio I did side by side comparison using <a href="http://audacity.sourceforge.net">Audacity</a>.</p>
<p><a href="https://svbtleusercontent.com/ntd27rfzghk80g.png"><img src="https://svbtleusercontent.com/ntd27rfzghk80g_small.png" alt="demodoutput.png"></a></p>
<p>From that comparison I noticed that <strong>demod-stdin</strong> introduces lag that seems to increase in time if 22050 samples per second output is used. It is marked with red arrows in the picture. My hypothesis is that multimon-ng is very strict about symbol timing. It should not be. Looking more at the <strong>demod-stdin</strong> source revealed that it is recommended to use 48000 sps output. Now there was another problem - multimon-ng only uses 22050 sps as input. So I digged into multimon-ng source and found out that it should be quite easy fix. Only one line might be changed in <a href="https://github.com/EliasOenal/multimon-ng/blob/master/demod_fsk96.c">demod_fsk96.c</a>:</p>
<pre><code class="prettyprint">//#define FREQ_SAMP 22050
#define FREQ_SAMP 48000
</code></pre>
<p>Indeed after this it started to decode AX25 packets using modified multimon-ng with the following command:</p>
<pre><code class="prettyprint">sox -t wav 9600RFdump.wav -esigned-integer -b16 -r 1024000 -t raw - | ./demod-stdin -mod NBFM -maxf 3500 -inrate 1024000 -outrate 48000 | multimon-ng -t raw -a FSK9600 /dev/stdin
</code></pre>
<h1 id="update-130115_1">Update 13.01.15 <a class="head_anchor" href="#update-130115_1">#</a>
</h1>
<p>Source is now available: <a href="https://github.com/andresv/demod">demod</a>, <a href="https://github.com/cubehub/multimon-ng">multimon-ng fork</a>.</p>
<p>Currently working command without doppler is:</p>
<pre><code class="prettyprint">rtl_fm -f 437.505M -M raw -s 1024000 | demod -mod NBFM -maxf 3500 -inputtype i16 -inrate 1024000 -outrate 48000 -channels 1 -squaredoutput | multimon-ng -t raw -a FSK9600 /dev/stdin
</code></pre>
<h1 id="update-230215_1">Update 23.02.15 <a class="head_anchor" href="#update-230215_1">#</a>
</h1>
<p><a href="http://andres.svbtle.com/liquiddsp-based-command-line-fm-demodulator">Here</a> you can read about better liquid-dsp based demodulator that I wrote from scratch.</p>
<h1 id="update-250215_1">Update 25.02.15 <a class="head_anchor" href="#update-250215_1">#</a>
</h1>
<p>Doppler is now ready, read about it from <a href="http://andres.svbtle.com/doppler-correction-tool-for-sdr">here</a>.</p>
tag:andres.svbtle.com,2014:Post/using-gnu-radio-to-show-iq-wav-file-on-waterfall-plot2014-11-09T05:11:16-08:002014-11-09T05:11:16-08:00Using GNU Radio to show I/Q wav file on waterfall plot<h1 id="installing-gnu-radio_1">Installing GNU Radio <a class="head_anchor" href="#installing-gnu-radio_1">#</a>
</h1>
<p>Finally I managed to get GNU Radio working on Mac OS X. Firstly I used Homebrew repository from here <a href="https://github.com/robotastic/homebrew-hackrf">https://github.com/robotastic/homebrew-hackrf</a> to install GNU Radio and HackRF tools. However it did not went well. Gnuradio-companion always crashed with the following error:</p>
<pre><code class="prettyprint lang-sh"> Warning: Block with key "xmlrpc_server" already exists.
Ignoring: /usr/local/Cellar/gnuradio/3.6.5.1/share/gnuradio/grc/blocks/xmlrpc_server.xml
Fatal Python error: PyThreadState_Get: no current thread
Abort trap: 6
</code></pre>
<p>It turned out that some of the libraries were still using Mac OS X system’s Python instead of Python installed using Homebrew. I got it to work after renaming system’s Python and rebuilding boost libraries.</p>
<pre><code class="prettyprint lang-sh">sudo mv /System/Library/Frameworks/Python.framework/ /System/Library/Frameworks/Backup.Python.framework
brew reinstall boost
</code></pre>
<p>Moreover <a href="https://github.com/robotastic/homebrew-hackrf">robotastic</a> fork uses old GNU Radio (3.6 series), however I found out that someone has made another fork which adds support for GNU Radio 3.7 series and also supports BladeRF devices. Unfortunately this fork did not have HackRF tools, therefore I made another fork cherry-picking best from both repositories. Here it is: <a href="https://github.com/andresv/homebrew-gnuradio">https://github.com/andresv/homebrew-gnuradio</a>.</p>
<h1 id="hdsdr-format_1">HDSDR format <a class="head_anchor" href="#hdsdr-format_1">#</a>
</h1>
<p>I wanted to use GNU Radio to take a closer look into ESTCube-1 <a href="http://to117.aai.ee/public_to117/Obs/SDR/HighRPM/">overpass recordings</a> that are recorded with <a href="http://www.hdsdr.de">HDSDR</a>. <a href="http://audacity.sourceforge.net">Audacity</a> was used to check in which format these recorded wav files were. Here is the screenshot from Audacity that shows sample rate as 390625 Hz and data type as 32 bit float.<br>
<a href="https://svbtleusercontent.com/jandwevdscddw.png"><img src="https://svbtleusercontent.com/jandwevdscddw_small.png" alt="Audacity.png"></a></p>
<p>Later I found out that this information is also in RIFF .wav header. Look <a href="http://www.moetronix.com/files/spectravue.pdf">page 64</a> for more details.</p>
<h1 id="gnu-radio_1">GNU Radio <a class="head_anchor" href="#gnu-radio_1">#</a>
</h1>
<p>GNU Radio graphical interface can be started with the following command:</p>
<pre><code class="prettyprint lang-sh">gnuradio_companion
</code></pre>
<p>Here are the blocks that are needed to display .wav file on waterfall plot:<br>
<a href="https://svbtleusercontent.com/yx6rwfwjs5ph0q.png"><img src="https://svbtleusercontent.com/yx6rwfwjs5ph0q_small.png" alt="gnuradioblocks.png"></a><br>
If you would like to build this by yourself I encourage you to use magnification icon on GNU Radio companion to search for needed blocks by name.<br>
<code class="prettyprint">N Channels parameter</code> in <code class="prettyprint">Wav File Source</code> block must be changed to 2 because our source file consists of 2 channels (I/Q data). Those I/Q channels must be converted to complex numbers because this is the most common format for GNU Radio signal blocks. As we can see <code class="prettyprint">QT GUI Waterfall Sink</code> also uses this as input format.<br>
<code class="prettyprint">Throttle</code> block is used to slow down or speed up data flow. Here variable <code class="prettyprint">samp_rate</code> (set to 390625 sps, because data in input file is sampled with that speed ) is used in <code class="prettyprint">Throttle</code> box configuration to feed data to <code class="prettyprint">Waterfall Sink</code> with the same rate as it is in input file. <code class="prettyprint">Waterfall Sink</code> can just be dropped in without configuring anything. Here is the output:<br>
<a href="https://svbtleusercontent.com/dr3ifbqahb89jw.png"><img src="https://svbtleusercontent.com/dr3ifbqahb89jw_small.png" alt="Screen Shot 2014-11-09 at 14.35.47.png"></a> <br>
Notice that at 50 kHz packets can be seen, although we know that ESTCube-1 transmits on 437.505 MHz. This is because there is no such frequency information in raw I\Q data. Very good explanation about I\Q data can be found <a href="http://whiteboard.ping.se/SDR/IQ">here</a>. Actual frequency where recording has been made, should be looked up from RIFF .wav file metadata field as described before in HDSDR chapter. If this is determined it can be used as <code class="prettyprint">center frequency</code> parameter in <code class="prettyprint">Waterfall Sink</code>. After that correct frequency is shown on waterfall plot.</p>
tag:andres.svbtle.com,2014:Post/turning-losi-124-buggy-into-autonomos-folkracer-part12014-10-08T10:27:17-07:002014-10-08T10:27:17-07:00Turning Losi 1/24 short course truck into autonomos folk racer [part 1]<p>This year’s <a href="http://www.robotex.ee">Robotex</a> is not far away. It takes place from 29 - 30 november. This time it will be special because I decided to participate. Actually I decided it last December after Robotex 2013 when I saw Folkrace competition. It is entry level competition where 5 autonomos cars race against each other. Robot that is fastest in completing 3 laps is declared as winner. Check a video from <a href="https://www.youtube.com/watch?v=xkleAJ7Tylw&spfreload=1">last year’s competition</a>. They are rather slow, we can do much better!</p>
<h1 id="chassis_1">Chassis <a class="head_anchor" href="#chassis_1">#</a>
</h1>
<p>My teammate <a href="https://github.com/fdkz">Elmo</a> suggested that we should use ready made RC car with brushless motor. Building mechanincs without proper tools is nightmare, therefore RC car platform is very reasonable choice for getting started as quickly as possible. We decided to use <a href="http://www.losi.com/Products/Default.aspx?ProdID=LOSB0242T2">Losi 1/24 short course truck</a>. It fits within 15x20 cm dimensions and it has brushless motor. We did not know at this time what else to look for.<br>
First thing was to replace NIMH battery with <a href="https://www.hobbyking.com/hobbyking/store/uh_viewItem.asp?idProduct=36220">Turnigy 7.4V LiPo</a> that is about the same size as original battery. It did not work with fully charged 2S LiPo, although Losi manual says that it works with 2S and 3S LiPos. Probably controller assumed that fully charged 2S LiPo is almost empty 3S LiPo and cut off the power from motor. However it did not cut off steering. After playing couple of minutes with steering servo LiPo was discharged a bit and motor controller started to work. It was massive difference compared to original NIMH battery. Wheels were spinning all the time.<br>
After playing it was time to strip down the vehicle and make it autonomous.<br>
<a href="https://svbtleusercontent.com/0gq1y69iyshhyq.jpg"><img src="https://svbtleusercontent.com/0gq1y69iyshhyq_small.jpg" alt="Losi_1_24.jpg"></a></p>
<h1 id="problems_1">Problems <a class="head_anchor" href="#problems_1">#</a>
</h1>
<p>The first major problem with this car is that it uses dumbed down 5 wire servo. Actually it can be seen from picture in product <a href="http://www.losi.com/Products/Default.aspx?ProdID=LOSB0242T">webpage</a>, however at the ordering time I even did not know that there are such servos available. 3 wire servo has little MCU inside enclosure that controls motor. 5 wire servo does not have it, therefore external controller must do it. Unfortunately Losi controller is not modular. It has 2.4 GHz receiver, servo controller and brushless controller all in one board. It does not make it hacker friendly product.</p>
<h1 id="servo-fix_1">Servo fix <a class="head_anchor" href="#servo-fix_1">#</a>
</h1>
<p>As I said doing mechanics without proper tools is hard. Losi servo has nice mechanical “clutch” that protects steering mechanism if for some reason servo turns too much. I thought that if another servo is going to be used this non standard clutch cannot be used and therefore there is higher chance to destroy steering. We came up with the plan to take out gears from Losi servo and put them to some other servo about the same size. Unfortunately I also had to solder Losi’s servo potentiometer to our Robbe servo because this special gear for clutch did not fit with Robbe’s shaft. Here modified Robbe servo with Losi’s clutch can be seen.<br>
<a href="https://svbtleusercontent.com/9trwg7g82qd1yg.jpg"><img src="https://svbtleusercontent.com/9trwg7g82qd1yg_small.jpg" alt="servo_clutch.jpg"></a></p>
<h1 id="modified-chassis_1">Modified chassis <a class="head_anchor" href="#modified-chassis_1">#</a>
</h1>
<p>After fixing servo it was time to drop in new <a href="https://www.hobbyking.com/hobbyking/store/uh_viewItem.asp?idProduct=32466">Turnigy TrackStar 25A motor controller</a>. I desoldered original brushless leads from Losi controller and put them back to Turnigy’s for hassle free motor interfacing. 2 sided tape was used to attach TrackStar to the left side of the frame. It could have been placed almost on the same spot as original controller however it would have increased height a little. I wanted to cover the frame with a plate where all sensors could be placed without too much trouble and it should be as low as possible for better handling. Here is a picture how it looks like without sensors:<br>
<a href="https://svbtleusercontent.com/wkqyjvs2z0riwq.jpg"><img src="https://svbtleusercontent.com/wkqyjvs2z0riwq_small.jpg" alt="frame.jpg"></a><br>
Teensy 3.0 is used as main controller, XBee is for sending telemetry and 5 Sharp (10-80 cm) IR sensors are used for navigation.<br>
<a href="https://svbtleusercontent.com/fiskcuc8xr6fq.jpg"><img src="https://svbtleusercontent.com/fiskcuc8xr6fq_small.jpg" alt="rustbuggy_sensors.jpg"></a></p>
<p>So far so good, next post will be about will it really work.</p>