Category: Ruby

Controlling Elgato Key Light under Ubuntu with Ruby

Recently I've acquired Elgato Key Light. It is a WiFi controllable LED lighting panel.

The panel uses 160 LEDs to provide up to 2800 lumens of brightness and a color range of 2900-7000K. While you can control it from a mobile device, doing it directly from the shell makes the whole experience way more convenient.

Key Light officially does not support Linux, however it uses ESP32, and it does run an unprotected HTTP server that accepts JSON commands. You can not only turn it on and off but also adjust both light temperature and brightness with simple JSON requests.

Here's the code that I use to control my lights written in Ruby:

#!/usr/bin/env ruby

require 'uri'
require 'net/http'
require 'json'
require 'yaml'

# Set your lights IPs
LIGHTS = %w[
  192.168.0.199
]

COMMAND = ARGV[0]
DETAIL1 = ARGV[1].to_i
DETAIL2 = ARGV[2].to_i

# Sends a json request to a given elgato light
def dispatch(ip, payload, endpoint, method)
  uri = URI("http://#{ip}:9123/elgato/#{endpoint}")
  req = method.new(uri)
  req.body = payload.to_json

  res = Net::HTTP.start(uri.hostname, uri.port) do |http|
    http.request(req)
  end

  puts res.body
end

def on(light_ip)
  dispatch(
    light_ip,
    { 'numberOfLights': 1, 'lights': [{ 'on': 1 }] },
    'lights',
    Net::HTTP::Put
  )
end

def off(light_ip)
  dispatch(
    light_ip,
    { 'numberOfLights': 1, 'lights': [{ 'on': 0 }] },
    'lights',
    Net::HTTP::Put
  )
end

def temperature(light_ip, value)
  raise "Needs to be between 143 and 344, was: #{value}" if value < 143 || value > 344

  dispatch(
    light_ip,
    { 'numberOfLights': 1, 'lights': [{ 'on': 1, 'temperature': value }] },
    'lights',
    Net::HTTP::Put
  )
end

def brightness(light_ip, value)
  raise "Needs to be between 3 and 100, was: #{value}"  if value < 3 || value > 100

  dispatch(
    light_ip,
    { 'numberOfLights': 1, 'lights': [{ 'on': 1, 'brightness': value }] },
    'lights',
    Net::HTTP::Put
  )
end

def info(light_ip)
  dispatch(
    light_ip,
    {},
    'accessory-info',
    Net::HTTP::Get
  )
end

def command(command, light_ip)
  case command
  when 'on'
    on(light_ip)
  when 'off'
    off(light_ip)
  when 'temperature'
    temperature(light_ip, DETAIL1)
  when 'brightness'
    brightness(light_ip, DETAIL1)
  when 'theme'
    temperature(light_ip, DETAIL1)
    brightness(light_ip, DETAIL2)
  when 'info'
    info(light_ip)
  else
    raise "Unknown COMMAND #{COMMAND}"
  end
end

LIGHTS.each { |light_ip| puts command(COMMAND, light_ip) }

You can place this code in /usr/local/bin under elgato name with executable permissions and then you can just:

elgato on # turn your lights on
elgato off # turn your lights off
elgato info # get info on your lights
elgato brightness 50 # set brightness to 50%
elgato temperature 280 # make  light temperature quite warm

Reading the uncompressed GZIP file size in Ruby without decompression

There are cases where you have a compressed GZIP file for which you want to determine the uncompressed data size without having to extract it.

For example, if you work with large text-based documents, you can either display their content directly in the browser or share it as a file upon request depending on the file size.

Luckily for us, the GZIP file format specification includes the following statement:

         +=======================+
         |...compressed blocks...| (more-->)
         +=======================+

           0   1   2   3   4   5   6   7
         +---+---+---+---+---+---+---+---+
         |     CRC32     |     ISIZE     |
         +---+---+---+---+---+---+---+---+

         ISIZE (Input SIZE)
            This contains the size of the original (uncompressed) input
            data modulo 2^32.

It means that as long as the uncompressed payload is less than 4GB, the ISIZE value will represent the uncompressed data size.

You can get it in Ruby by combining #seek, #read and #unpack1 as followed:

# Open file for reading
file = File.open('data.gzip')
# Move to the end to obtain only the ISIZE data
file.seek(-4, 2)
# Read the needed data and decode it to unsigned int
size = file.read(4).unpack1('I')
# Close the file after reading
file.close
# Print the result
puts "Your uncompressed data size: #{size} bytes"

Cover photo by Daniel Go on Attribution-NonCommercial 2.0 Generic (CC BY-NC 2.0). Image has been cropped to 766x450px.

Copyright © 2022 Closer to Code

Theme by Anders NorenUp ↑