Monday, January 29, 2018

Side project: getting eyes far away

Last year I became obsessed with FPV. I don't mean games - that's kinda for kids. I'm talking here about getting an object, a plane in this case, equipped with a camera and other equipment that can be controlled from a distance. I was trying to get into the multi-rotor business but that seems not to be my thing. Planes, on the other hand, are. So I tried my luck there...

My first time was... well... exciting wouldn't even begin to cover it! It was a mysterious experience! I was actually in a plane that flies around, hundreds of meters away from me, seeing the real life flashing before my eyes. It was simply a blast! But as soon as I flew the first time and saw the land that I already covered something woke up in me: the nagging question what is out there? I guess I am kind of an explorer down deep inside :) And so I begun to cover more and more ground. It soon was pretty clear for me that the equipment I had at the time with a 200mW VTX was not going to quite cut it. All that was made crystal clear for me once I lost the plane after just several hundred meters flight in the neighborhood. It sucked big time and about 600 PLN was forever lost. Sometimes when I drive around I hope to see this plane hanging out in some tree but that didn't happen.

Someone could say: this is way too expensive and too dangerous - but not me. I came back home, late at night that day, and immediately ordered a stronger VTX (used to ride a 200mW one, now I ordered a 600mW ImmersionRC that people said was good for several kilometers!). It took a while before I was able to get back up in the sky but it eventually happened. With the new VTX, a changed site and a new plane I was rocking the sky again!

Over the course of last summer I didn't loose a plane (well, technically I did but more on that later or). I was flying first 1.3km to a landmark I knew and I was passionate to get to. That didn't work because of the antennas that I had. That day I learned that power is not everything - you need to have something to convey it with. And in the case of video transmission (and also the control transmission from your radio to the plane) antennas are the holy grail! So I went ahead and bought 3 of the most recommended (and most sexy) ones - the Aomway clover leave antennas. Alongside the 600mW VTX I also bought a 14dBi directional patch antenna. Using this setup I was finally able to easily break the 2km range!

But my appetite (and the addiction) grew...

So I started experimenting. First about going further than 2km. It turned out that the 100mW OpenLRS system wasn't all that good at ground leve. - so I built a 2m mast that gave me more range. A few weeks later I tested it up to 4.8km and let me tell you the wiggles that I have had that day were nothing compared to the first time with a woman! I was so fucking stressed out I almost fainted!

All things considered it was a pretty good flight. I used a 3s 3000mAh Li-on pack at that flight and it almost lasted to get the plane home. Just a few tents of meters over a field and the plane was safe and sound in my hands. That meant I needed more power. Lot's of it.

In a situation like that power comes from a combination of input voltage, amps, ESC, motor and propeller. So I started looking for the best combination. Just out of share luck I have had a few od 2212 810kv motors around (from my failed attempt to build a 450-sized quadcopter). I thought, since it had the required max thrust (around 1kg) so why I don't I give it a shot. And so I did!

The first attempt was very promising. At 4S and a 10x4.5 prop it flew great and I was able to finally reach the 5km mark. I turned back to see how the battery will behave on the way but it was plenty left. That encouraged me to give it another shot the next day and... that is how I achieved my up-to-date record of 8.2km out and safely back!

Oh boy was I excited! All my efforts were finally paying off! My dream was to take the plane from the place where I was usually flying back to the place I live - which was about 6.5km away. With the record flight of 8.2km I thought it was a no brainer. Man, was I wrong! It's not just the tx/rx/antennas combination but first and foremost the lay of the land that allows (or not) to get to longer distances. Luckily the day before that I have put my phone number and since the plane automatically landed on a busy street and a good man picked it up and called me the plane was recovered - and my ambition was greatly humbled.

This year I'm trying my luck in the same direction that my record flight of 8.2km was. I am hoping to break through the 20km barrier with some new equipment that I have bought:

  • 500mW Wolfbox TX (with potential to be modded to 1W)
  • 1W VTx and a biquad 11dBi antenna that I tested up to 12km with crystal clear picture on the ground
  • an all new Mini Talon with 4S2P Li-on battery pack

The plan is as follows:

  • First do a maiden of the plane with the minimum electronics to get it to fly
  • Add a flight controller for stabilization and get it to work as expected (F4 AIO FC with OSD)
  • Do a test flight over a 1km distance with the lest powerful antennas I have to check the viability of the video down and control up link
  • Do a endurance test with all equipment to see how long will the battery will last at what speeds - hope to get at least 40km range before the batteries are all dead)
  • Get back to the 5km mark and back
  • Get past the 10km mark and back
  • Get past the 15km mark and get back
  • Get past the 20km mark and get back

I have no idea if this is going to work or not. I truly don't! But I hope one day I'll have a video to show you how the flight went and what emotions it gave me!

Happy flying!

Sunday, January 21, 2018

Creating your own ESLint shareable configuration

This is going to be a quick one: How to share ESLint configuration between projects.

Step 1: create a separate project

By separate I mean really separate with its own package.json and own repository.

$ mkdir eslint-config-config-name
cd eslint-config-config-name
npm init
...
About to write to .../package.json:

{
  "name": "eslint-config-config-name",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes)

Step 2: create your configuration

Since the main in package.json points to index.js this will be the spot where we put our shareable configuration. Let's start small - you can always extend it further later on as you work your way through the available rues:

module.exports = {
  rules: {
    'no-var': 'error',
  }
}

To make sure you don't forget about installing ESLint in your target project add the following lines to the package.json of your configuration project:

  "peerDependencies": {
    "eslint": ">= 4"
  }

This will ensure that when the target project doesn't have ESLint as a dependency then an error will be thrown telling that it needs to be present.

Step 3: publish your repository

In order for the configuration to be available for everyone you need to have it somewhere everyone can access it. In that case it is recommended to add the following keywords to your package.json.

  "keywords": [
    "eslint",
    "eslintconfig"
  ]

If you choose to publish it to NPM then that's fine, but honestly those config files can get pretty specific for your company therefore I would suggest just tagging and pushing to your Git repository, like so:

$ git init
git add .
git commit -a -m "Initial import"
git tag 1.0.0
git remote add origin url-to-your-repo
git push --set-upstream origin master

Make sure your repository is accessible via HTTP or HTTPS - that's going to be needed for the next step, which is...

Step 4: use the config

In order to use that preset you need to add it as a dependency or devDependency to your project:

  "devDependencies": {
    ...
    "eslint-config-config-name": "git+https://url-to-your-repo#1.0.0"
  }

That way you are not specifying just the latest version but a specific version. This is done so that projects can adopt the changes to your common configuration as they get the time for it.

Next we need to make use of the configuration in our local project. We do that by creating either a local .eslintrc.js or by adding that configuration to package.json. Since we have already created an example .eslintrc.json in the previous step let's see it being used in package.json:

  "eslintConfig": {
    "extends": [
      "eslint-config-config-name"
    ]
  }

That's it! Now when you npm install your dependencies then the configuration and all its dependencies will be installed along with it.

If you would like to see a working example take a look at Vue.js + Electron example on Github that uses the common configuration I prepared for my company.

Happy linting!

Sunday, January 14, 2018

Electron - where Vue.js meets desktop

Today I was pushed by some unseen force to check out Electron - the desktop platform that embeds Chrome for everything. Let me tell you - it is amazing! Not only you have just one browser to care about but it automatically runs on all sorts of platforms! Well, I am sold on the idea - how about you? If you're like me then keep reading.

Getting started

First we need a sort of web application. For obvious reasons I am choosing Vue.js (don't even get me started on Angular X or React - I could bitch about them forever). So Vue.js it is. Obviously we need a build system for it. And what better can we get than POI! So let's do a Vue.js app.

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install ` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (vue-electron) 
version: (1.0.0) 
description: 
entry point: (index.js) main.js
test command: poi test
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to .../vue-electron/package.json:

{
  "name": "vue-electron",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "poi test"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes) 

$ npm install --save-dev poi poi-class-component

Since this will be an Electron app we need the electron package too:

$ npm install --save-dev electron

Now we're all set to create our app. In the src folder let's create 2 files: index.js and App.vue

src/index.js

import App from './App.vue'

new App({ el: '#app' })

src/App.vue

<template>
  <h1>{{ message }}</h1>
</template>

<script>
import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class App extends Vue {
  message = "Hello, world!"
}
</script>

<style>
h1 {
  background-color: green;
}
</style>

Easy, right? Now here comes the hard part: embedding the application in Electron. I mean, it's not really hard if you know how :). There is a good documentation for both Electron and POI on how do to it but nowhere is there one place that shows the whole solution. So there you have it.

poi.config.js

module.exports = {
  entry: './src/index',
  webpack(config) {
    config.target = 'electron-renderer'
    return config
  },
  homepage: './',
}

main.js

const electron = require('electron')
const isDev = require('electron-is-dev')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
const path = require('path')
const url = require('url')

let mainWindow

function createWindow () {
  mainWindow = new BrowserWindow({width: 800, height: 600})

  const entryDev = 'http://localhost:4000'
  const entryProd = url.format({
    pathname: path.join(__dirname, 'dist/index.html'),
    protocol: 'file:',
    slashes: true
  })
  mainWindow.loadURL(isDev ? entryDev : entryProd)

  if (isDev) mainWindow.webContents.openDevTools()

  mainWindow.on('closed', function () {
    mainWindow = null
  })
}

app.on('ready', createWindow)

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', function () {
  if (mainWindow === null) {
    createWindow()
  }
})

Now we need a few things to be able to run it. First there needs to be a way of starting the web application and then the Electron container hosting our app. We'll do that by using the concurrently module and by adding a start script to our package.json. While we're at it let's also install electron-is-dev package used in the main.js script:

$ npm install --save-dev concurrently
$ npm install --save electron-is-dev

Since the electron-is-dev package will be part of our Electron app and will not be bundled with the rest of the app then it needs to be added as a regular, transitive, dependency.

Now the promised start script. Remember, that goes into the "scripts" section in your package.json

"start": "concurrently \"poi\" \"sleep 10 && electron .\""

Please note the sleep 10 before starting electron. That is to give the initial bundler run enough time to start serving our app.

And that is it! Your Vue.js app runs in Electron! And yes, it does hot reloading! Isn't that cool? Well... How about giving your app to other people? You wouldn't do that this way as you do in development, would you? We need some bundling techniques. Luckily for us there is a package called electron-packager that has our backs covered.

Bundling

Let's start by installing the electron-packager module:

$ npm install --save-dev electron-packager

With that covered let's add a few more scripts:

"build": "npm run clean && npm run compile && npm run package",
"clean": "rm -rf dist $npm_package_name-*",
"compile": "poi build --no-clear",
"package": "electron-packager . --ignore=\"src|poi.config.js|.gitignore\""

I think they are pretty self explanatory. Now from the console when you start the npm run build command it will first remove all leftovers, then build the client application using POI and then package the application for your platform. If you would like to additionally package your app for a different operating system then you can always start the package target with additional parameters, like so:

$ npm run package -- --platform=win32 --arch=all

And that is it! Electron is super cool - go check it out! And as a reward for getting to those last words here's a link to a GitHub repository that has a ready-made working example: https://github.com/padcom/vue-electron-example. Besides the mentioned settings it also contains ESLint integration that I described in my previous post.

Happy coding!

Saturday, January 13, 2018

POI, Vue and ESLint

Writing code is hard. It is however much harder to write code that conforms to standards that are already in the project. Fortunately for us there is ESLint with all the bells and whistles that help in that tedious task. And believe it or not there are ready-made presets and plugins that make the configuration really simple.

Installation

First, as usual, we need some packages added to our project:

$ npm install --save-dev eslint poi-preset-eslint eslint-plugin-vue babel-eslint
Then create or update your existing poi.config.js with as follows:
module.exports = {
  presets: [
    require('poi-preset-eslint')({ mode: '*' }),
  ]
}

Configuration

All that is left is to configure ESLint. I prefer to have a set of rules that don't get in the way while developing but hold the forth when building final version. That is why some of my settings are environment-dependant:

// This defines the mode of operation for checks depending on the environment
const mode = process.env.NODE_ENV === 'production' ? 'error' : 'warn'

module.exports = {
  parserOptions: {
    'parser': 'babel-eslint',
    'sourceType': 'module'
  },

  extends: [ 'eslint:recommended', 'plugin:vue/recommended' ],

  rules: {
    'camelcase': [ 'error', { 'properties': 'always' } ],
    'func-name-matching': 'error',
    'func-names': [ 'error', 'never' ],
    'object-shorthand': [ 'error', 'always' ],
    'prefer-const': 'error',
    'prefer-template': 'error',
    'template-curly-spacing': [ 'error', 'never' ],
    'no-useless-rename': 'error',
    'no-useless-constructor': 'error',
    'arrow-spacing': [ 'error', {
      'before': true,
      'after': true,
    } ],
    'arrow-parens': [ 'error', 'as-needed' ],
    'arrow-body-style': [ 'error', 'as-needed' ],
    'no-const-assign': 'error',
    'prefer-numeric-literals': 'error',
    'indent': [ 'error', 2, {
      'SwitchCase': 1,
    } ],
    'semi': [ 'error', 'never' ],
    'quotes': [ 'error', 'single' ],
    'comma-dangle': [ 'error', 'always-multiline' ],
    'no-console': mode,
    'no-debugger': mode,
    'no-alert': mode,
    'no-var': 'error',
    'one-var': [ 'error', 'never' ],
    'space-before-function-paren': [ 'error', {
      'anonymous': 'never',
      'named': 'always',
      'asyncArrow': 'always',
    }],
    'object-curly-spacing': [ 'error', 'always' ],
    'array-bracket-spacing': [ 'error', 'always' ],
    'computed-property-spacing': [ 'error', 'never' ],
    'key-spacing': [ 'error', {
      'beforeColon': false,
      'afterColon': true,
    } ],
    'keyword-spacing': 'error',
    'space-infix-ops': 'error',
    'space-unary-ops': 'error',
    'space-in-parens': [ 'error', 'never' ],
    'comma-spacing': [ 'error', { 'before': false, 'after': true } ],
    'no-whitespace-before-property': 'error',
    'no-multi-spaces': 'error',
    'no-multiple-empty-lines': [ 'error', { 'max': 1 } ],
    'dot-location': [ 'error', 'property' ],
    'getter-return': 'error',
    'consistent-return': [ 'error', { 'treatUndefinedAsUnspecified': true } ],
    'valid-jsdoc': 'error',
    'eqeqeq': 'error',
    'no-return-assign': [ 'error', 'always' ],
    'vue/max-attributes-per-line': [ 'error', {
      singleline: 5,
      multiline: { max: 5, 'allowFirstLine': true },
    } ],
    'vue/html-indent': [ 'none' ],
    'vue/attribute-hyphenation': [ 'error', 'never' ],
  },
}

As for the rules (and remember this is a personal preference), I don't like the fact of being forced to write every single attribute for a component in a separate line. I think this looks awful. Sure, if there are more than X attributes then it should be in a separate line. That's why the number is so high (5). Also, I found that the hyphenation of attributes that are bound, like :selection-mode causes compilation errors so this one is (at least currently) broken.

Practice

A hint for transitioning projects from non-eslint to eslint: when you'll start with the project there will be a massive number of violations. I mean like hundrets of them. Before you commit to fixing them all consider selectively disabling ESLint in all files by means of the following directive:

/* eslint-disable */

Then, when you will get the time remove that directive from 1 file and fix warnings. Then move to the next until ESLint is enabled in all the files. That will make the job much more approachable!

And that is it! Of course you can add or change more rules as you see fit. Whatever works for you.

Happy coding!