PDA

View Full Version : Sharp Aquos TV control driver creation



Yoggi
August 19th, 2015, 07:28 AM
Hi Ron and co,

At the moment I have a rudimentary driver working thanks to Girder’s built-in ‘transport’ and my ambition is that this driver should be configurable from Girder’s Device manager (configurable with a minimum of scripting). I am hoping that you and others on this forum can assist (these posts would hopefully be useful for others to).

I see this as a learning experience as I am fare from being a programmer so I am sure there are plenty of things that could be done differently and more efficient/maintainable code wise.

Driver
Some Sharp Aquos TV’s has RS232 and IP control, utilizing simple protocol.

E.g. to check if the TV is on or off one would send ‘POWR? ’ and the TV would respond ‘0’ for off and ‘1’ for on. All commands and acknowledge commands are terminated with a carrier return ‘\r’ so it is easily parsed with transports built-in parser.

The control protocol also have a few shortcomings like the lack of volume up and down, the workaround is to query the TV’s volume, add or subtract to this and then set the volume to this new value.



To do

1. Implement volumeUp and volumeDown.

2. Save TV’s status and pass status (like current channel, volume, input, on off etc.) on to NetRemote (store values in a Lua table when TV is queried).

3. Create a status/heartbeat function (keep connection alive, TV disconnects after 3min of inactivity) Should also update status for Power, Volume, Mute, Input, Sleep.

4. Automatic log in if TV asks for user and password (If TV is set up for user and password).
TV sends 'Login:'
Girder should send id .. "\r" .. password .. "\r" (needs to be done in one string)

4. …


Questions

1.The volume up and down is causing me the biggest headache at the moment. I have based my script on the Pio1a example and I am using the sendCFF( self, cmd, callback ) function to send commands.

How do I get a queried value back from function sendCMD( self, cmd, callback ) in order to send a set volume command without getting out of sync (with the returned queried volume value) ? I have not been able to figure this out. I assume I need TXR.TX_REQUEUE_FIRST

I have a function ('adjust_volume' see attached lua file) that takes volumeUp or volumeDown as a parameter and I call it with volumeUp.

First I need to get current volume by sending 'VOLM? ' and when I get the volume value (e.g. ‘59’ received) back I need to add one to it and send the set command 'VOLM60 '.


2. How do I pass a value from a Lua script to NetRemote (to change a text label, a picture or a button)?

Help and comments would be much appreciated!

Joachim

Ron
August 20th, 2015, 09:10 AM
1. Hmm, well just send the get command before you even send the set command... does that work?

2. You can use the KV system to send values to NetRemote.

Yoggi
August 20th, 2015, 10:01 AM
Hi Ron,

Maybe it is as simple as that (to call get and after that call set). But what about the "que", isn’t there a chance to get out of sync?

If I have a 'current' transaction (a non persistent) that blocks and then I send a persistent one, what then? Will the persistent one end up in the que and get sent after the blocking one is finished? Or do we have two ques?

From the Girder manual
“Note that multiple transaction can be queued up and sit in the queue until the connection is established.

At any one time there can only be one 'current' transaction. That is a transaction that
blocks sending of other transactions until it has either timed-out or received a response.
There can be any number of transactions in the "persistent" queue. What happens after a
timeout or data received events depends completely on the script and the return value it
sends back to it's transaction.”

I will look into the KV system!

Joachim

Ron
August 20th, 2015, 10:06 AM
I think the persistent transaction will get queued up in the persistent transaction queue immediately. (note a persistent transaction shouldn't send anything)

Yoggi
September 7th, 2015, 03:19 AM
Hi Ron,

(recap)
To increase or decrease volume I need to get the current volume and then add or subtract to it and then set the volume to the new volume.

Therefore I first need to call sendCommand ('VOLM? ') (from function volumeAdjust) to get the volume (e.g. '7'). Then I need to build string 'VOLM8 ' and send this again with sendCommand (from function volumeAdjust)

If I call sendCommands two times in a row, the second sendCommands is called before the date is received back from the first sendCommand call so this doesn’t work.
Question… after some more reading … can I use TX_CONTINUE (Pass the data to the next transaction.) in the above sentence?

If I neste the second sendCommand within the first sendCommands it seems to work see (code below). Will this cause problems? Is there a more correct way of doing this?

I have read the Transport section in the manual many times but I can’t fully grasp how it works. Anny chance you could extend this section in the manual?


Full driver code is in the attached document.


function volumeAdjust( self, value ) -- '1' or '-1'

sendCommand( self, 'VOLM? ', function( success, returnData )
print( "volume get success? ", success, "returnData", returnData )

if returnData ~= nil and returnData ~= 'ERR' then -- TV replies 'ERR' if off

returnData = returnData + value

-- TV volume values 0-60
if returnData < 0 then
returnData = 0
elseif returnData > 60 then
returnData = 60
end

returnData = tostring( returnData )

while #returnData < 4 do returnData = returnData .. ' ' end -- pad up e.g. '7' to '7 '

sendCommand( self, 'VOLM' .. returnData, function( success ) -- success (boolean
print ( "volume set success? ", success, "volume set", returnData )
end)
end
end)

end



function sendCommand( self, cmd, callback ) -- e.g. 'POWR? ' or 'POWR1 ' (sends valid Aquos TV commands of 8 characters)

if not self.connection then
return false
end

if not callback then
callback = function( ) end
end

local retries = 0

local tx = self.connection:newTransaction(
function()
-- sent
print("sendIP / Data was sent...")
end,
function( data )
-- data

-- returning data e.g. OK, ERR, 0, 1, 2, 0-60, 0-150

-- commands that return numbers
-- POWR? (0, 1)
-- VOLM? (0-60)
-- MUTE? (1, 2)
-- OFTM? (0-150) sleep timer
-- IAVD? (ERR, 1-8) input

if data == "OK" then -- TV received and acknowledge command
print("Command sent", cmd, "returnData", data )
callback( true )

elseif data == "ERR" then -- TV received invalid command
print("Invalid command, command sent", cmd, "returnData", data )
callback( false, data ) -- added data as returnData
return TXR.TX_REMOVE

-- TV queries return numbers, do we have a number between 0 and 150
elseif tonumber( data ) >= 0 and tonumber( data ) <= 150 then

-- what cmd was called POWR?, VOLM?, MUTE?
if cmd == 'POWR? ' then

if data == '0' then
print ("POWR_OFF")
-- callback( true )
callback( true, data )

elseif data == '1' then
print ("POWR_ON")
-- callback( true )
callback( true, data )

end

elseif cmd == 'VOLM? ' then
print("VOLUME", data)
callback( true, data )

end

else
print("Error could not send")
callback( false )
return TXR.TX_REMOVE -- Remove the transaction
end

end,
function ()
-- timeout
retries = retries + 1
if retries > 2 then
print("sendIP / Failed to get devices")
callback( false )
return TXR.TX_REMOVE -- Remove the transaction
else
print("sendIP / Retrying...")
return TXR.TX_REQUEUE_FIRST -- Re-queue the transaction at the beginning of the transaction queue.
end
end
)

tx:timeout(5000) -- time between retries
tx:data(cmd .. "\r")
tx:persistent(false)

self.connection:send(tx, true)

end

Yoggi
September 11th, 2015, 08:07 AM
I don't want to give up on Girder but I need to understand Girder’s built-in ‘transport’ better. I am at the technical level that I can see that Girder should be able to do all that I need but I am not a professional programer (if I was I would probably not need a product like Girder but I would write my own code from scratch).

I understand that promixis is a small company but I wounder how many people have given up on Girder do to the lack of documentation (I have a pm from one on this forum that has given up do to this).

Maybe free Girder licensees could be given to people that write good tutorials just like the offer for translation of Girder.

I think you have a grate product (a lot of nice features) so please help users use it to its fullest with just as good documentation and examples!

Joachim

Ron
September 11th, 2015, 08:37 AM
Hi Joachim,



If I call sendCommands two times in a row, the second sendCommands is called before the date is received back from the first sendCommand call so this doesn’t work.
Question… after some more reading … can I use TX_CONTINUE (Pass the data to the next transaction.) in the above sentence?



Data flow:
When a packet of data arrives ( and a packet is defined by the parser ) it first is given to the persistent transactions. If they return TX_CONTINUE the data will continue to flow to the next persistent transaction. If all persistent transactions returned TX_CONTINUE the packet goes to the current transient transaction (transient = NOT persistent). If any transaction wants to consume the data it should not return TX_CONTINUE. If a transaction is done it should return TX_REMOVE

At any one time there is only ONE transient transaction. So even if you queue two transient transactions only the first of these will be the current transient transaction. returning TX_CONTINUE from it will simply keep it as the current transaction and the 2nd transaction is not used. Once the current transaction times out or returns TX_REMOVE the 2nd will come into play.


The code you posted in post 5 with the nested command looks exactly as I would do it too. First queue up a transient transaction that queries for the volume level, parse the result then send a volume modify transaction from the callback from the first get volume transaction.