Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crow as II-follower #250

Merged
merged 5 commits into from Nov 19, 2019
Merged

Crow as II-follower #250

merged 5 commits into from Nov 19, 2019

Conversation

@trentgill
Copy link
Collaborator

@trentgill trentgill commented Nov 8, 2019

crow now successfully receives i2c commands and queries from other i2c leader devices. Has been tested only with two crows talking to each other, either by switching leader/follower roles dynamically, or by one device leading another & querying values.

Current usage (to be added to docs):

--- leader crow talks to another crow
-- same as other i2c devices. see the list with: ii.crow.help()
function tell_crow_to_do_things()
  ii.crow.output(1,3.2) -- set output 1 to 3.2 volts
  ii.crow.slew(2,100) -- set output 2 to a slew time of 100ms
    -- using ms as an integer to work with Teletype natively :/
  ii.crow.call1(3) -- generic call to crow (signed 16bit integers)
  -- call2 and call3 supported, up to...
  ii.crow.call4(4,3,2,1) -- note floats are *not* supported to make this teletype native
end

function ask_crow_for_info()
  ii.crow.get('input',2) -- get voltage at input[2]
  ii.crow.get('output',3) -- get voltage at output[3]
  ii.crow.get('query0') -- send a generic query with no args
  -- query1 & 2 up to..
  ii.crow.get('query3',5,4,3) -- query crow's response to the provided args
end

-- all getters return a single value in the 'data' arg below
ii.crow.event = function( e, data )
  if e == 'input' then -- the voltage of a remote input (float)
  elseif e == 'output' then -- the voltage of a remote output (float)
  elseif e == 'query0' then -- the response to a zero-arg query (16bit integer)
  elseif e == 'query3' then -- the response to a 3-arg query (16bit integer)
  end
end

When implementing crow's i2c follower behaviour:

-- calls to `output` and `slew` automatically set their respective params (eg output[1].volts = _)
ii._c.call1 = function(a)
  -- do something with the received value 'a'
end

ii._c.call4 = function(a,b,c,d)
  -- do something with up to 4 args
  -- eg: 'a' could be the index into a table of functions that take up to 3 args
end

-- for queries, 'input' and 'output' are handled by the C driver, so lua only sees queryN requests:
-- NOTE: these functions are called directly from the driver & *must* return their response
-- they will block the i2c bus until they return (which will lockup a TT request, but not a crow)
ii._c.query0 = function()
  return unique_id() -- here we use the 'query0' command to return this crow's unique id
end

ii._c.query3 = function(a,b,c)
  -- here we just show that we have access to the lua env:
  return a+b*c
end

First question is whether there is a better name for 'this crow' than '_c' (which is a shortcut to the crow table i was using elsewhere, but has largely been deprecated). Suggestions welcome. We could change the follower commands to ii.crow and leaders to something different ii.remotecrow. or use ii.this or ii.self for the OO crew.

Second question is whether the queryN callbacks should share a single callback like the ii.<device>.get() commands as they are conceptually similar. Key differences are that queryN returns N args rather than just one, so we'd need to use varargs (...). Secondly queryN functions must return the i2c response from that function call. If no return value is provided they will return zero.

I note there is a danger here that query messages could crash crow if the lua environment is currently active when the i2c interrupt occurs. To avoid this, we'd need to overhaul the event system to have a priority stack which feels like overkill until we start seeing crashes (i haven't seen any). As crow is a leader/follower you can send a callN message to a crow which knows to respond with a matching callN which is fine for crow<->crow, but not Teletype (as it can't currently act as a follower). This issue does not apply to 'input' and 'output' queries as they fetch responses in the C layer.

Adding Teletype support (#55) should now be straightforward for anyone who wants to jump on that, though decisions about naming here should be reflected there.

I2C chaining (#5) should now be relatively simple to support by adding some crowlib commands to set reception i2c address. Note that sending arbitrary Lua is not yet possible as the i2c lib only buffers messages up to 9bytes (1 cmd byte, and 8 arg bytes).

Fixes #17
Fixes #220
Improves (but doesn't necessarily close) #221 (up to 16 queued commands won't be dropped)

Here's an example script where 2 crows work together to count as fast as possible. It will make druid eat a lot of CPU cycles as it processes & displays the data.

--- I2C Handshake

function init()
    ii.crow.get('input')
end

ii.crow.event = function(e,n)
    if e == 'input' then
        if n > input[1]() then
            countup()
        end
    end
end

function countup()
    ii.crow.call1(0)
end

ii._c.call1 = function(a)
    print(a)
    ii.crow.call1(a+1)
end
@trentgill trentgill requested review from ngwese, simonvanderveldt and tehn Nov 8, 2019
@simonvanderveldt
Copy link
Member

@simonvanderveldt simonvanderveldt commented Nov 8, 2019

First question is whether there is a better name for 'this crow' than '_c' (which is a shortcut to the crow table i was using elsewhere, but has largely been deprecated). Suggestions welcome. We could change the follower commands to ii.crow and leaders to something different ii.remotecrow. or use ii.this or ii.self for the OO crew.

ii.this or ii.self makes the most sense to me.

Possibly stupid question but the callN functions and and queryN arguments are fixed? Or can anyone create whatever they want?

And is there a reason we can't use something like ii.crow.input[n].volts and ii.crow.output[n].volts to mimic the regular queries instead of using .get()?

@tehn
Copy link
Member

@tehn tehn commented Nov 8, 2019

super rad, nice work!

opinions:

  1. self
  2. single callback

i'd like to give this a prelim test phase before doing the TT side, to make sure we like the syntax.

@tehn
tehn approved these changes Nov 8, 2019
@trentgill
Copy link
Collaborator Author

@trentgill trentgill commented Nov 9, 2019

ii.self sounds good.

@simonvanderveldt
The callN and queryN functions are limited by the i2c driver-interface. We have to buffer the values so that multiple calls don't block execution waiting for the i2c bus which means a finite cap on message length & thus argument count. 4 args seems like overkill already.

We could collapse them into a single ii.crow.call() and ii.crow.query() but that would require some magic under the hood. My reason for explicit numbering is that the user could explicitly use the functions for different classes of action. I just tried writing some examples and they didn't make sense immediately, so perhaps example scripts are needed before we make this decision?

An extended version of call (with a different name & usage) could be possible for sending chunks of lua between connected crows, but i think this will be implemented at the C layer, and exposed through lua hooks (as it doesn't make much sense to write lua code that sends lua code...). I think call and query are already difficult enough as it is, because they are basically indexed functions where you have to store names / functions in comments. There is more discussion on these questions in #17.

@tehn
re: Single callback: I'm unsure what the most fluid version of this would be. Thoughts / suggestions?

-- most programmer-y
ii.self.query = function(...)
    local args = {...}
    local nargs = #args
    -- user can switch on nargs, or just handle them all the same?
    return _
end

-- basic helper for count-of-args so it's obvious how to switch based on 'query0' vs 'query2'
ii.self.query = function(count,...)
    local args = {...}
    -- switch on count
    return _
end
-- args are explicit to avoid varargs (which are weird in that you have to explicitly table-ify them
ii.self.query = function(count,a,b,c)
    -- switch on count
    -- a,b, and/or c may be nil
    return _
end

-- provide args wrapped in a table with a count
ii.self.query = function(count,arg-table)
    -- switch on count
    -- use args[n] where n=1:3 (args[n] can be nil)
    return _
end
@trentgill
Copy link
Collaborator Author

@trentgill trentgill commented Nov 11, 2019

@simonvanderveldt @tehn updated to ii.self for the follower callbacks.

@simonvanderveldt
Copy link
Member

@simonvanderveldt simonvanderveldt commented Nov 11, 2019

ii.self looks good!

I just tried writing some examples and they didn't make sense immediately, so perhaps example scripts are needed before we make this decision?

The main thing I'm not sure about is what it would look like when used in practice/which cases are relevant, so I think some examples are a good idea.

@trentgill trentgill merged commit f15fd7d into master Nov 19, 2019
@trentgill trentgill deleted the i2c-follower branch Nov 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

3 participants