Skip to content

Commit

Permalink
organize source
Browse files Browse the repository at this point in the history
  • Loading branch information
neroist committed Mar 13, 2024
1 parent 3334936 commit 85a259a
Show file tree
Hide file tree
Showing 18 changed files with 882 additions and 796 deletions.
48 changes: 48 additions & 0 deletions src/commands/brightness.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import std/httpclient
import std/strformat
import std/terminal
import std/json

import puppy

import ../common

proc brightness*(device: int = 0; brightness = -1; output: bool = on): int =
## Set device brightness

if not isSetup(output) or not checkDevices(device, output = output): return

let
apiKey = readFile(keyFile)
devices = parseJson readFile(devicesFile)
(deviceAddr, model) = getDeviceInfo(devices, device)

if brightness == -1: # if brightness is default value
let response = getDeviceState(deviceAddr, model, apiKey)

if output:
echo fmt"Device {device} brightness: ", response["data"]["properties"][2]["brightness"].getInt(), '%'

return response["data"]["properties"][2]["brightness"].getInt()

if brightness notin 1..100 :
if output:
error "Invalid brightness, is not in the range 1-100"

return

let body = %* {
"device": deviceAddr,
"model": model,
"cmd": {
"name": "brightness",
"value": brightness
}
}

let re = put(ControlURI, @{"Govee-API-Key": apiKey, "Content-Type": "application/json"}, $body)

if output:
sendCompletionMsg re.code, parseJson(re.body)["message"], HttpCode(re.code)

return brightness
94 changes: 94 additions & 0 deletions src/commands/color.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import std/httpclient
import std/strformat
import std/strutils
import std/terminal
import std/random
import std/colors
import std/json

import puppy

import ../common

proc color*(device: int = 0; color: string = ""; output: bool = on): string =
## Set device color with an HTML/hex color code.
## NOTE: when called with no parameters, the device's current color will be #000000 if:
##
## 1. Music mode is on.
## 2. color temperature is not 0.
## 3. A scene is playing on the device.

if not isSetup(output) or not checkDevices(device, output = output): return

let
apiKey = readFile(keyFile)
devices = parseJson readFile(devicesFile)
(deviceAddr, model) = getDeviceInfo(devices, device)

var
color = color.replace(" ").replace("-").normalize()
colorJson = %* {"r": 0, "g": 0, "b": 0}
r, g, b: int

if color == "":
let response = getDeviceState(deviceAddr, model, apiKey)

try:
colorJson = response["data"]["properties"][3]["color"]
except KeyError: discard

let
color = $rgb(
colorJson["r"].getInt(),
colorJson["g"].getInt(),
colorJson["b"].getInt()
)

if output:
echo fmt"Device {device} color: ", colorToAnsi(parseColor(color)), color, esc

return color

block checks:
if color notin ["random", "rand"]:
if color.isColor():
color = $color.parseColor()
break checks

if color[0] != '#':
color = '#' & color

if not color.isColor():
if output:
error fmt"{color[1..^1]} is an invalid color."

return

if color in ["random", "rand"]:
r = rand(255)
g = rand(255)
b = rand(255)
else:
(r, g, b) = parseColor(color).extractRGB()

let body = %* {
"device": deviceAddr,
"model": model,
"cmd": {
"name": "color",
"value": {
"r": r,
"g": g,
"b": b
}
}
}

let re = put(ControlURI, @{"Govee-API-Key": apiKey, "Content-Type": "application/json"}, $body)

if output:
echo fmt"Set device {device}'s color to ", colorToAnsi(rgb(r, g, b)), rgb(r, g, b), esc, '\n'

sendCompletionMsg re.code, parseJson(re.body)["message"], HttpCode(re.code)

return color
85 changes: 85 additions & 0 deletions src/commands/color_temp.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import std/httpclient
import std/strformat
import std/terminal
import std/json
import std/math

import puppy

import ../common

func kelvinToRgb*(temp: int): tuple[r, g, b: range[0..255]] =
## Converts color temperature to rgb

# Algorithm from https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html

let temp = temp.clamp(1000, 40000) / 100

if temp <= 66:
result.r = 255
result.g = int (99.4708025861 * ln(temp) - 161.1195681661).clamp(0.0, 255.0)
else:
result.r = int (329.698727446 * (pow(temp - 60, -0.1332047592))).clamp(0.0, 255.0)
result.g = int (288.1221695283 * (pow(temp - 60, -0.0755148492))).clamp(0.0, 255.0)

if temp >= 66:
result.b = 255
elif temp <= 19:
result.b = 0
else:
result.b = int (138.5177312231 * ln(temp - 10) - 305.0447927307).clamp(0.0, 255.0)

proc colorTemp*(device: int = 0; output: bool = on; temperature: int = -1): int =
## Set device color temperature in kelvin

if not isSetup(output) or not checkDevices(device, output = output): return

let
apiKey = readFile(keyFile)
devices = parseJson readFile(devicesFile)
(deviceAddr, model) = getDeviceInfo(devices, device)

if temperature == -1:
let
response = getDeviceState(deviceAddr, model, apiKey)

temp = response["data"]["properties"][3]["colorTemInKelvin"].getInt(0)

ansi = colorToAnsi(kelvinToRgb(temp))

if output:
echo fmt"Device {device}'s color temperature is ", ansi, temp, 'K', esc

return temp

let
jsonColorTemRange = devices[device]["properties"]["colorTem"]["range"]
colorTemRange = jsonColorTemRange["min"].getInt() .. jsonColorTemRange["max"].getInt()

if temperature notin colorTemRange:
if output:
# .a is slice lower bound, .b is slice upper bound
error fmt"Color temperature {temperature}K out of supported range: {colorTemRange.a}K-{colorTemRange.b}K"

return

let body = %* {
"device": deviceAddr,
"model": model,
"cmd": {
"name": "colorTem",
"value": temperature
}
}

let
re = put(ControlURI, @{"Govee-API-Key": apiKey, "Content-Type": "application/json"}, $body)

ccolor = colorToAnsi(kelvinToRgb(temperature))

if output:
echo fmt"Set device {device}'s color temperature to {ccolor}{temperature}K{esc}", '\n'

sendCompletionMsg re.code, parseJson(re.body)["message"], HttpCode(re.code)

return temperature
26 changes: 26 additions & 0 deletions src/commands/devices.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import std/strutils
import std/terminal
import std/sugar
import std/json

import ../common

proc devices* =
## Get list of devices and their properties

if not isSetup(true): return

let devices = parseJson readFile(devicesFile)

for dev, i in devices.getElems():
let
cmds = collect(for i in i["supportCmds"]: i.getStr())
## seq of all supported commands of the device

echo "\e[1m", "DEVICE ", $dev, ansiResetCode
echo " Mac Address: ", i["device"].getStr()
echo " Model: ", i["model"].getStr()
echo " Device Name: ", i["deviceName"].getStr()
echo " Controllable: ", capitalizeAscii($(i["controllable"].getBool()))
echo " Retrievable: ", capitalizeAscii($(i["retrievable"].getBool()))
echo " Supported Commands: ", cmds.join(", "), "\n"
47 changes: 47 additions & 0 deletions src/commands/other.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## other, kinda-related commands that don't do much

import std/browsers

import puppy

import ../common

proc version* =
## Get Nova current version

echo "Nova ", Version

proc about* =
## Nova about

echo "Nova ", Version, '\n'
echo Description
echo "Made by ", Author, '.'

proc description* =
## Prints Nova's description

echo Description

proc source* =
## View Nova's source code

openDefaultBrowser("https://github.com/neroist/nova/blob/main/src/nova.nim")

proc repo* =
## View Nova's GitHub repository

openDefaultBrowser("https://github.com/neroist/nova/")

proc license*(browser: bool = false) =
## View Nova's license

if browser:
openDefaultBrowser("https://github.com/neroist/nova/blob/main/LICENSE")
else:
echo fetch("https://raw.githubusercontent.com/neroist/nova/main/LICENSE")

proc docs* =
## View Nova's documentation

openDefaultBrowser("https://neroist.github.io/nova/")
26 changes: 26 additions & 0 deletions src/commands/picker.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import std/strutils
import std/random
import std/colors

import tinydialogs

import ../common
import ./color

proc picker*(device: int = 0; setProperty: bool = true; output: bool = on) =
## Pick a color through a GUI (your OS's default color picker dialog)

let
pickedColor = colorChooser(
"Pick a color",
[rand(0..255).byte, rand(0..255).byte, rand(0..255).byte]
)

if output:
echo "Picked ", colorToAnsi(parseColor(pickedColor.hex)), toUpper pickedColor.hex, esc

if setProperty:
if output: echo ""

discard color(device, pickedColor.hex, output)

Loading

0 comments on commit 85a259a

Please sign in to comment.