2019-11-15 11:57

Yoctopuce Hardware Project

Being fairly busy these days with Releasewise during the day, and often nights and weekends too, I started looking for a small tinkering project to put my mind on something else. Earlier this year I bought a small display and light sensor from Yoctopuce. Time to make some software!

Components

Yocto-MaxiDisplay

The display has 128 x 64 resolution with crisp, green pixels.

Yocto-Light-V3

This sensor is capable of measuring ambient light, returning the measurement in lux. You can actually split the board and move the light sensor chip a few meters away from the remaining (largest) part of the board (requires soldering 4 wires).

Goals

I set some modest goals:

  • Display the pathetically small number of Twitter followers for @releasewise (hint!)
  • Display the number of page views for releasewise.com using the Simple Analytics API.
  • Display remaining days until the end of the month.
  • Measure the ambient light in my office and adjust the brightness of the roof LED panels, using a Milight WIFI bridge controller.

Code

Driving the display

To talk to the Yoctopuce components you need to run their VirtualHub software. Once you have this running you can send HTTP requests to a tiny web server, which transmits low-level commands via USB to the Yoctopuce devices it finds on the USB bus.

For example, this sends Hello to the display:

http://localhost:4444/bySerial/YD128G64-F2BFF/api/display?command=!Hello

(your device ID will be different)

I don’t know why, but the Yoctopuce designers have chosen a weird and poorly documented way to send instructions across HTTP requests. I have plucked the commands I needed from this source code rather than relying on the manual.

The commands for the display I needed are:

  • ^ (or %5E escaped) to clear the display
  • &Small.yfm, &Medium.yfm, and &Large.yfm to set the font size (use %26 to escape the &)
  • m10,10,118,54 to create 10-pixel margin
  • !Foo to print Foo (use %0A to insert line breaks)

Driving the light sensor

The obtain a light sensor reading you call brightness.json which returns a JSON response having currentValue as one of its values (the unit is lux).

Yoctopuce has an odd way of dealing with getting and setting values. For example:

Getting the brightness:

  • http://localhost:4444/bySerial/YD128G64-F2BFF/api/display/brightness.json

Setting the brightness:

  • http://localhost:4444/bySerial/YD128G64-F2BFF/api/display?brightness={value}

You can’t POST a JSON request, everything runs with GETs. Oh well.

Reading page views

Reading the page views for the past 30 days is easy:

SIMPLE_ANALYTICS_ENDPOINT = 'https://simpleanalytics.com/'

def self.pageviews
  response = Typhoeus.get("#{SIMPLE_ANALYTICS_ENDPOINT}releasewise.com.json",
    headers: {
      'api-key': ENV['SIMPLE_ANALYTICS_API_KEY'],
      'content-type': 'content-type: application/json'
    })
  result = JSON.parse(response.body)
  result['pageviews']
end

Obtaining follower count

After spending 10 minutes reading up on Twitter’s API shenanigans I gave up and started looking for a hack.

It turns out, there is a trick to obtain the follower account of any account without resorting to the API. Of course this will be deprecated some day but for now it works:

TWITTER_ENDPOINT = 'https://cdn.syndication.twimg.com/widgets/followbutton/info.json?screen_names=releasewise'

def self.followers_count
  response = Typhoeus.get(TWITTER_ENDPOINT)
  result = JSON.parse(response.body)
  result[0]['followers_count']
end

Sweet!

Counting remaining days

# Days until end of month
last_day = Date.civil(Time.new.year, Time.new.month, -1).day

YoctopuceDisplay.clear
YoctopuceDisplay.small_font
YoctopuceDisplay.print "Days left in #{Date::MONTHNAMES[Time.new.month]}", true
YoctopuceDisplay.large_font
YoctopuceDisplay.print "#{last_day - Time.now.day}"

As you can see I created a couple of helper methods to clean up the code.

Adjust LED panel brightness

I have 4 LED panels in my office which, at full brightness, are too bright for a dark day. I found a nice Ruby gem which offers an API for the Milight WIFI bridge I mentioned before.

I still need to tweak the translation between the measured ambient light (coming from the windows) and the brightness, hue, and saturation of the LED panels. Perhaps I can borrow ideas from f.lux and Apple’s Night Shift.

For now I’ve settled for a simple algorithm that translates the measured brightness to a percentage, which I subtract from 100 in order to adjust the brightness of the 4 LED panels:

require 'milight/v6'
controller = Milight::V6::Controller.new(BRIDGE_IP)
controller.all.brightness(100 - brightness_pct)`

Result

Permalink — Comments or kudos? Find me on Twitter.

Are you a product maker? Use Releasewise’s 25 unified tools to supercharge your projects and teams.


2019-09-27 10:10

A Basic Rails Action Cable Example

In this post I’ll show you how to setup and use Rails Action Cable. While there are many articles about Action Cable I felt there’s room for a short and practical how-to. I’ll probably do a follow-up article where I’ll use AnyCable instead.

Prerequisites

  • A fresh Rails 6 app

Goals

  • Sending a message to a specific user
  • Broadcasting a message to all “connected” users

Flow

  • A controller action generates a random number, acting as a user ID
  • The action renders a page, displaying this random ID
  • The page has two buttons:
    • A “Page me” button. Clicking this button tells the server to broadcast a message back to this specific user.
    • A “Page all” button. Clicking this button broadcasts a message to all users.

If you open a second browser tab you’ll be able to verify that broadcasting to a specific user and to all users works as advertised.

Code

I must admit I had a tiny hiccup getting Action Cable working on my local machine due to the fact that the Async adapter would not transmit anything.

Once I switched from Async to Redis the problem was solved. Since not using Async on your production environment is recommended anyway this is not a big deal. In fact, you should always strive for as much parity between your development environment and your staging & production environments according the excellent The Twelve Factors manifesto.

So let’s begin by adding the Redis gem to your Gems file:

gem 'redis'

Note: pin your gem versions outside an example project like this.

Next, ensure the Action Cable railtie is loaded by inspecting application.rb. If you see require 'rails/all' you’re good to go. If you’re like me and want to specify which railties you need and don’t need then ensure the list looks more or less like this:

require "rails"
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
require "sprockets/railtie"
require "rails/test_unit/railtie"

Ensure action_cable/engine is present in this list.

Next up is creating a test view:

<h1>User <%= @rnd_user_id %></h1>

<p><%= button_to("Page me", page_me_path(user_id: @rnd_user_id), remote: true) %></p>

<div id="hello-user-<%= @rnd_user_id %>"></div>

<p><%= button_to("Page all", page_all_path, remote: true) %></p>

<div id="hello-all-users"></div>

Don’t forget to add the necessary routes to your routes.rb file:

Rails.application.routes.draw do
  root to: 'landing#index'
  mount ActionCable.server => '/websocket'
  post :page_me, to: 'landing#page_me'
  post :page_all, to: 'landing#page_all'
end

Add this to application.js:

//= require action_cable
//= require jquery3
//= require rails-ujs

/* global $, App, ActionCable */
this.App = {}
const cablePath = document.querySelector('meta[name=action-cable-url]').getAttribute('content')
App.cable = ActionCable.createConsumer(cablePath)
App.webNotificationsChannel = App.cable.subscriptions.create(
  {
    channel: 'WebNotificationsChannel'
  },
  {
    received (data) {
      console.log(data)
      if (data.userId === null) {
        $('#hello-all-users').html(`The time for all users is ${data.time}`)
      } else {
        $(`#hello-user-${data.userId}`).html(`The time for you is ${data.time}`)
      }
    },
    connected () {
      console.log('connected')
    },
    disconnected () {
      console.log('disconnected')
    }
  }
)

Don’t forget to add the cable meta tag to the application.html.erb layout. It is used by application.js to set the Action Cable endpoint:

<%= action_cable_meta_tag %>

Next, let’s write the necessary code:

Add a file called web_notifications_channel.rb to the app/channels directory:

class WebNotificationsChannel < ApplicationCable::Channel
  def subscribed
    Rails.logger.info("WebNotificationsChannel subscribed")
    stream_from("web_notifications")
  end
  def unsubscribed
    Rails.logger.info("WebNotificationsChannel unsubscribed")
  end
end

Add the following code to the controller which renders the test page:

def index
  @rnd_user_id = SecureRandom.hex(6)
end

def page_me
  ActionCable.server.broadcast("web_notifications", userId: params[:user_id], time: Time.now.to_i)
  head(:no_content)
end

def page_all
  ActionCable.server.broadcast("web_notifications", userId: nil, time: Time.now.to_i )
  head(:no_content)
end

Restart the server and go to http://localhost:5000/ (the port might differ on your machine):

Open the browser’s development tools pane, it should display:

connected

Also, take a look at your development.log file (e.g. via tail -f log/development.log). It should contain something like:

WebNotificationsChannel subscribed
WebNotificationsChannel is transmitting the subscription confirmation
WebNotificationsChannel is streaming from web_notifications
WebNotificationsChannel transmitting {"userId"=>nil, "time"=>1569571022} (via streamed from web_notifications)
WebNotificationsChannel transmitting {"userId"=>"b3adf6ff392f", "time"=>1569571501} (via streamed from web_notifications)

Note: "userId"=>nil is sent when clicking "Page all" and "userId"=>"..." is sent when clicking "Page me".

Now click the buttons to see if they all work. Open a second tab and check what happens inside each tab:

This is all you need to start using Action Cable!

Permalink — Comments or kudos? Find me on Twitter.

Are you a product maker? Use Releasewise’s 25 unified tools to supercharge your projects and teams.