Logic Machine Forum
Script integration for Acrylic Amp & Upstream audio device - Printable Version

+- Logic Machine Forum (https://forum.logicmachine.net)
+-- Forum: LogicMachine eco-system (https://forum.logicmachine.net/forumdisplay.php?fid=1)
+--- Forum: Scripting (https://forum.logicmachine.net/forumdisplay.php?fid=8)
+--- Thread: Script integration for Acrylic Amp & Upstream audio device (/showthread.php?tid=4519)



Script integration for Acrylic Amp & Upstream audio device - tigi - 20.01.2023

This script is still a work in progress, there is some unused code and optimization is certainly possible, but it already functions.

This script makes it able to control (on/off, switch source, volume) of your Acrylic Amp4 device from pushbuttons on a wall switch 
You can custom define 5 web radio stations and the text that will be displayed on the pushbutton 

Prerequisites:
  • An Acrylic Amp4/Upstream audio device connected to your lan and speakers
       
  • A zigbee, knx, enocean,... relay switch, can be a controllable AC plug
       
  • A pushbutton that can display custom text such as MDT Glasstaster (II) Smart
    Though any pushbutton will work but without station name feedback
       

The script needs 4 pushbuttons, these related group addresses have a tag attached such as 'audio_bath':
  • 1 for on/off
  • 1 for switching between sources/stations
  • 2 for up/down volume

Event script triggered by tag such as 'audio_bath'
Code:
-- https://developer.arylic.com/httpapi/#http-api
ip = '192.168.1.100' -- IP Address of Acrylic Upstream AMP Device
require('json')
require('user.up2stream')
require('socket.http')
local DEBUG = false
-- list of stations to choose from, limited to 5 radio stations and 2 special sources (usb and wifi)
-- special care for usb and wifi naming which cannot be renamed since used in script to construct the correct http api url
-- though they can be replaced by 2 extra radiostations, in the 4Stream Android app you can change source also
local stations = {
 
  {'Radio2','http://icecast.vrtcdn.be/ra2ovl-high.mp3'},
  {'QMusic','https://playerservices.streamtheworld.com/api/livestream-redirect/QMUSIC.mp3'},
  {'Nostalgie','http://playerservices.streamtheworld.com/api/livestream-redirect/NOSTALGIEWHATAFEELINGAAC.aac'},
  {'StuBru','http://icecast.vrtcdn.be/stubru-high.mp3'},
  {'ZenFM','https://23613.live.streamtheworld.com/TOPZEN.mp3'},
  {'usb','3'},
  {'wifi','wifi'}
  }

local StationSelected -- The Current selected station number, based on Table Key number
local channelname -- The Channel name, the first nested table field from stations table
local grp_onoff_obj = '5/0/1' -- Button on/off object for device
local grp_onoff_FB_obj = '5/1/1' -- Button on/off Feedback object for device
local grp_channelswitch_obj = '5/0/2' -- Button channel switching object for device
local grp_channelswitch_FB_obj = '5/1/2' -- Button channel switching Feedback object for device
local grp_volumeUP_obj = '5/0/4' -- Button Volume UP object for device
local grp_volumeDOWN_obj = '5/0/5' -- Button Volume DOWN object for device
local grp_volumeUP_FB_obj = '5/1/4' -- Button Volume UP Feedback object for device
local grp_volumeDOWN_FB_obj = '5/1/5' -- Button Volume DOWN Feedback object for device
local grp_switch_obj = '8/6/10' -- AC Plug Switch on/off object for device
local grp_trigger_obj = event.dst -- Grp object that triggered this script, since script is triggered by TAG we need to identify the grp object
local message_obj = '8/5/6' -- MDT Glass Pushbutton ASCII Message Grp Address to custom name pushbutton based on state, here used for displaying channel name / radio station
local RoomChannel = 'Channel_Badk'

AudioTb = storage.get('Audio_UpStream') -- Get audio table from storage (where last station is stored)
if type(AudioTb) ~= 'table' or next(AudioTb) == nil then -- if no table exists
  AudioTb = {} -- create an empty table
  AudioTb[RoomChannel] = 1 -- set initial station to 1
end

-- DEVICE ON/OFF BUTTON
if grp_trigger_obj == grp_onoff_obj then -- if on/off button is pressed
  if event.getvalue() then -- if group object is true / ON
    if DEBUG == true then log('activate') end
    grp.checkwrite(grp_switch_obj, true) -- switch AC plug to ON
    grp.checkwrite(grp_onoff_FB_obj, true) -- on/off group object feedback to ON
    grp.checkwrite(message_obj, '1/3') -- show waiting message above pushbutton
    pingdevice(ip) -- first check and wait till device is pingable
    grp.checkwrite(message_obj, '2/3')  -- see prev comment
    internetup() -- second check and wait till internet is up for device
    grp.checkwrite(message_obj, '3/3')  -- see prev comment
    os.sleep(2) -- Optionally give the device some extra time to be ready to receive to weblink
    StationSelected = AudioTb[RoomChannel] -- Load the last stored selected station from table
    -- Check which station was last used, if usb or wifi use specific api url string, else use webradio url string
    if stations[StationSelected][1] == 'usb' then -- if first field of nested table has 'usb' in it
      station = Up2_PlayUSB..stations[StationSelected][2] -- use specific link and construct on selected file in stations list
      channelname = stations[StationSelected][1] -- set channelname to 'usb' (from stations table list)
    elseif stations[StationSelected][1] == 'wifi' then -- if first field of nested table has 'wifi' in it
      station = setPlayerCmdSource..stations[StationSelected][2] -- use specific link and construct to play from 4Stream App or Spotify...
      channelname = stations[StationSelected][1] -- set channelname to 'wifi' (from stations table list)
    else -- if anything else than usb or wifi we assume its a web radio station
      station = setPlayerCmdWebRadio..stations[StationSelected][2]  -- use specific link and construct to play selected radio station from stations table list
      channelname = stations[StationSelected][1] -- set channelname to radio station name from stations table list
    end   
    grp.checkwrite(message_obj, channelname) -- show selected channelname message above pushbutton
    socket.http.TIMEOUT = 70
    socket.http.request(station) -- send command url to device to start playing the last stored station or source
  else -- if group object is false / OFF
    if DEBUG == true then log('uitschakelen') end
    grp.checkwrite(grp_onoff_FB_obj, false) -- set grp object device on/off feedback to false
    grp.checkwrite(grp_switch_obj, false)   -- set grp object AC plug on/off to false, switching off
    grp.checkwrite(message_obj, 'goodbye') -- show goodbye message above pushbutton
    os.sleep(3) -- wait for 3 seconds
    grp.checkwrite(message_obj, false) -- show nothing above pushbutton
    grp.checkwrite(grp_channelswitch_FB_obj, 0) -- set grp object channel button switch feedback to 0 / false
  end
end

-- CHANNEL SELECTION BUTTON
if grp_trigger_obj == grp_channelswitch_obj then -- if channel switch button is pressed
  grp.checkwrite(grp_channelswitch_FB_obj, 1) -- set channel switch group object feedback to 1 / on
  StationSelected = AudioTb[RoomChannel] + 1 -- get value from table/storage and add 1
  if StationSelected == 8 then StationSelected = 1 end -- if value is 8 reset to 1, the stations list is limited to 'only' 7 items

  if stations[StationSelected][1] == 'usb' then -- see prev comment
    station = Up2_PlayUSB..stations[StationSelected][2] -- see prev comment
    channelname = stations[StationSelected][1] -- see prev comment
  elseif stations[StationSelected][1] == 'wifi' then -- see prev comment
    station = setPlayerCmdSource..stations[StationSelected][2] -- see prev comment
    channelname = stations[StationSelected][1] -- see prev comment
  else -- see prev comment
    station = setPlayerCmdWebRadio..stations[StationSelected][2] -- see prev comment
    channelname = stations[StationSelected][1] -- see prev comment
  end

  if DEBUG == true then log(channelname,station) end

  socket.http.TIMEOUT = 70
  socket.http.request(station) -- see prev comment
 
  AudioTb[RoomChannel] = StationSelected -- update the table with the selected channel
    grp.checkwrite(message_obj, channelname) -- see prev comment
  os.sleep(1)
  grp.checkwrite(grp_channelswitch_FB_obj, 0) -- see prev comment
    storage.set('Audio_UpStream',AudioTb) -- write table to storage
 
end

-- VOLUME UP / DOWN BUTTONS
if grp_trigger_obj == grp_volumeUP_obj and grp.getvalue(grp_onoff_obj) == true then -- if UP button pressed and device is on
  grp.checkwrite(grp_volumeUP_FB_obj, 1) -- set volume up switch group object feedback to 1 / on
  grp_volume = tonumber(getdata('','vol')) -- get the current volume from device (function in user lib script)
  if grp_volume >= 80 then -- if volume is more than 80
        grp_volume = 80 -- keep volume at 80, is loud enough
  else
    grp_volume = grp_volume + 2 -- if not, set volume 2 notches higher
    end
  socket.http.TIMEOUT = 70
  socket.http.request(Up2_VolSet..grp_volume) -- send command url to device to set volume higher
  grp.checkwrite(grp_volumeUP_obj, 0) -- set volume up switch group object to 0 / off
  grp.checkwrite(grp_volumeUP_FB_obj, 0) -- set volume up switch group object feedback to 0 / off
elseif grp_trigger_obj == grp_volumeDOWN_obj and grp.getvalue(grp_onoff_obj) == true then -- if DOWN button pressed and device is on
  grp.checkwrite(grp_volumeDOWN_FB_obj, 1) -- set volume down switch group object feedback to 1 / on
  grp_volume = tonumber(getdata('','vol')) -- see prev comment
  if grp_volume <= 5 then -- if volume is less than 5
    grp_volume = 5 -- keep volume at 5, is quiet enough
  else
    grp_volume = grp_volume - 2 -- if not, set volume 2 notches lower
  end
  socket.http.TIMEOUT = 70
    socket.http.request(Up2_VolSet..grp_volume) -- send command url to device to set volume lower
  grp.checkwrite(grp_volumeDOWN_obj, 0) -- set volume down switch group object to 0 / off
  grp.checkwrite(grp_volumeDOWN_FB_obj, 0) -- set volume down switch group object feedback to 0 / off
end

User library script named user.up2stream
Code:
local Path = 'http://'..ip..'/httpapi.asp?command=' -- Link base path

-- in use
local getPlayerStatus = Path..'getPlayerStatus'
local getStatusEx = Path..'getStatusEx'
local setPlayerCmdSource = Path..'setPlayerCmd:switchmode:' -- add wifi, line-in, bleutooth, optical, pcusb, udisk, line-in2l
local setPlayerCmd = Path..'setPlayerCmd:'
local setPlayerCmdWebRadio = Path..'setPlayerCmd:play:'
local Up2_VolSet = Path..'setPlayerCmd:vol:'
local Up2_ShowVol = Path..'setPlayerCmd:Vol--n'
local Up2_PlayUSB = Path..'setPlayerCmd:playLocalList:' -- Add INDEX number (song number) to end
local Up2_SwitchPBSource = Path..'setPlayerCmd:switchmode:' --Add bluetooth, line-in, optical, udisk, wifi

-- Function to ping device with timeout (to see if device is already online after power-up)
function socketping(ip, port, timeout)
  port = port or 80
  local sock = require('socket').tcp()
  sock:settimeout(timeout or 20)
  local res, err = sock:connect(ip, port)
  --log(res)
  --log(err)
  sock:close()
  --status = err
  --return res, err
  if (res) then return true end
  return false
end

-- Function to replace a character in a string
-- Here used to get the group feedback address from the event group address that triggered this event
-- Only works when group address and group address feedback have the same first and last number such as 5/0/1 and 5/1/1
function replace_char(pos, str, r)
    return str:sub(1, pos-1) .. r .. str:sub(pos+1)
end

-- function to convert hex to character (for Acrylic http api)
function hextochar(str)
  local hex_to_char = {}
  for idx = 0, 255 do
    hex_to_char[("%02X"):format(idx)] = string.char(idx)
    hex_to_char[("%02x"):format(idx)] = string.char(idx)
  end
 
  str = str:gsub("(..)", hex_to_char)
  str = string.gsub( str, '&lt;', '<' )
  str = string.gsub( str, '&gt;', '>' )
  str = string.gsub( str, '&quot;', '"' )
  str = string.gsub( str, '&apos;', "'" )
  str = string.gsub( str, '&#(%d+);', function(n) return string.char(n) end )
  str = string.gsub( str, '&#x(%d+);', function(n) return string.char(tonumber(n,16)) end )
  str = string.gsub( str, '&amp;', '&' ) -- Be sure to do this after all others
  return str
end

function pingdevice(ip, timeout)
  local startTime = os.time()
  local online = false
  local i = 1
  if timeout == nil then timeout = 50 end
  while online == false and (os.time() - startTime < timeout) do
    local pingOutput = os.execute("ping -c 1 -W 1 " .. ip)
    -- Check ping successful by exit code (0 is success, anything else is failure)
    if pingOutput == 0 then
        online = true
       -- log("Audio Device is UP")
    else
        --log('countdown retry:'..(timeout-i))
        i = i + 1
    end
  end
  if online == false then
  --log("Audio Device is DOWN or timeout reached.")
  end
end

function internetup(timeout)
  --log('please wait')
  local startTime = os.time()
  local online = false
  local i = 1
  if timeout == nil then timeout = 60 end
  socket.http.TIMEOUT = 70
  while online == false and (os.time() - startTime < timeout) do
    data, status, err = socket.http.request(getStatusEx)
   -- log(status)
    if status == 200 and data ~= 'unknown command' then
      data = json.decode(data)
      local internet = tonumber(data.internet)
      if internet == 1 then
        online = true
        status = 'online'
        --log('device is online and has internet')
       
      end
    else
      --log('countdown: '..(timeout - i))
      os.sleep(1)
        i = i + 1
     
    end
  end
-- log(status)
end

function getdata(command, key)
  if command == nil then command = 'getPlayerStatus' end
  if key == nil then key = 'mode' end
    data, status, err = socket.http.request(getPlayerStatus)
    --log(status)
      data = json.decode(data)
      return data[key]
end