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

Net_Node.send crashes when its argument "to" is a list #93

Closed
cxrodgers opened this issue May 24, 2021 · 16 comments
Closed

Net_Node.send crashes when its argument "to" is a list #93

cxrodgers opened this issue May 24, 2021 · 16 comments

Comments

@cxrodgers
Copy link
Contributor

Background: I am trying to control 8 speakers. Current approach is to use one Pilot and three Child, each controlling two speakers.

Right now, I'm just trying to connect one Child to the Pilot. I'm using tasks.gonogo as an example. However, I think it may (?) have a bug in it.
https://github.com/wehr-lab/autopilot/blob/dc9833ae827301219d50620fa8e1b22266bb222d/autopilot/tasks/gonogo.py#L170

Note that the to argument is a list. The documentation of Net_Node.send says this is acceptable. But it crashes here:
https://github.com/wehr-lab/autopilot/blob/dc9833ae827301219d50620fa8e1b22266bb222d/autopilot/core/networking.py#L1582
when it tries to convert the list using bytes.

File "/home/pi/dev/autopilot/autopilot/core/networking.py", line 1582, in send
    self.sock.send_multipart([self.upstream.encode('utf-8'), bytes(msg.to, encoding="utf-8"), msg_enc])
TypeError: encoding without a string argument

What is the correct syntax for sending messages to a Child? I tried using the name of the child (e.g., "rpi02") as well as "T_rpi02" and the name of the Child task's Net_Node. Then the message sends okay but it doesn't get received. Although this might be a networking problem, I can debug further if I understand better what the to field should be. Thanks!

@cxrodgers
Copy link
Contributor Author

Update: I copied an earlier line of code from gonogo.py, the line that initializes the child. This uses key='CHILD'.
https://github.com/wehr-lab/autopilot/blob/dc9833ae827301219d50620fa8e1b22266bb222d/autopilot/tasks/gonogo.py#L126

This message is received by the child, and I can encode arbitrary information in the "value" field, which is great! However, the received key is always set to "START" by something (the Station?), and I need to be able to set it to something in listens so that I can call a function.

This is the received message:

DEBUG:core.networking.Net_Node._rpi02:RECEIVED: ID: rpi02_4; TO: _rpi02; SENDER: rpi02; KEY: START; FLAGS: {}; VALUE: {'foo': 'bar', 'subject': 'testmouse', 'inner_key': 'WAIT'}

Note that the KEY is START. I tried specifying an "inner_key" in VALUE but I don't think this did anything. What I would like to do is set the KEY to "wait", and attach that to a handle in listens. But if I set the KEY to anything other than "CHILD", the message isn't received properly.

        # Three possible mesages from the parent
        self.listens = {
            'WAIT': self.l_wait,
            'PLAY': self.l_play,
            'STOP': self.l_stop,
        }

@sneakers-the-rat
Copy link
Contributor

sneakers-the-rat commented May 24, 2021

perfect timing. just got finished with another project and am turning to autopilot to fix a few bugs & wrap up 0.4.0.

tl;dr:

long version:

yes it is high time to re-architect the networking modules. i have this sketched in as v0.5.0, which will be to a) make a unitary inheritance system so that b) everything can be given a unique id so that c) it's a lot easier to send messages to arbitrary recipients, eg. being able to do to='agent_id.object_id'.

Originally, the idea was to have a treelike network structure, where everything has a well-defined 'parent' except the apex node (typically the terminal) which only has children. The idea was that it would make network topologies easier to keep track of/make routing easier, but that almost immediately proved to be wrong and very limiting, so there are a number of hacks to send messages directly, and having them all sort-of work but have completely unclear logic is probably the worst of all possible worlds.

So the way it works is that net nodes all send messages 'through' a station object, which runs in a separate process and handles things like repeating dropped messages so they don't clog up the main agent process (i also think this should be rebuilt...). so there's an attempt at abstraction around the zmq frames that are built that, for net nodes, defaults to "actually" sending the message to the parent station object and the intended recipient is set in the to field in a serialized Message object, see https://github.com/wehr-lab/autopilot/blob/dc9833ae827301219d50620fa8e1b22266bb222d/autopilot/core/networking.py#L1579-L1582

'multihop' messages (ie. to is a list) were sort of half-implemented, where they should be formatted as zmq frames as

[hop_0, hop_1, ..., hop_n, final_recipient, serialized_message]

but net nodes don't respect that, and stations don't either. i'll work on fixing that now.

The second question is related, where when trying to send to another agent it's ambiguous whether it should go 'up the tree' or 'down the tree' (though it works if the 'downstream' node has already connected to the agent in question so it's in the senders dictionary, that's just very brittle), so the 'CHILD' key is used so that the message is explicitly sent to the child, which has obvious problems of both elegance and practicality. The problem here is zmq related: there's a basic asymmetry between zmq.ROUTER and zmq.DEALER sockets. ROUTER sockets are bound to a particular ip and port that they can receive messages at from any socket that connects to them, but DEALER sockets connect to a port bound by another zmq socket, so they can send and receive messages only from the connected socket. Net_Node objects use DEALER sockets because the assumption was that we'll be sending messages to the Station object that has a ROUTER socket.

So that's all very messy and i'll fix it along with ^^ so that you can multihop from the pilot to the specific object you're intending to control.

For 0.5.0 i'll be changing the logic so that net_nodes always bind a port with a router, and just search for any unbound port and then notify the rest of the swarm that it exists and how to contact it, etc. then it's actually not a huge deal/computationally expensive to just open a bunch of dealer sockets on the fly to anyone we want to communicate with

@cxrodgers
Copy link
Contributor Author

Wonderful, thanks for the explanation!

Concerning your workaround using L1263, I modified the message to be as follows:

        self.node.send(
            to=prefs.get('NAME'),
            key='CHILD',
            value={'foo': 'bar', 'subject': self.subject, 'KEY': 'WAIT'},
            )    

However I then get this error

Traceback (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.7/threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "/home/pi/dev/autopilot/autopilot/core/networking.py", line 1264, in l_child
    KEY = msg.value['keys']
KeyError: 'keys'

I suspect it's because on Lines 1263-4 you have

if 'KEY' in msg.value.keys():            
    KEY = msg.value['keys']

But you might have meant:

if 'KEY' in msg.value.keys():            
    KEY = msg.value['KEY']

In any case, if I also add a key called "keys" with the same value as the key called "KEY":

        self.node.send(
            to=prefs.get('NAME'),
            key='CHILD',
            value={'foo': 'bar', 'subject': self.subject, 'KEY': 'WAIT', 'keys': 'WAIT'},
            )    

I get a little bit further, but then this happens:

ERROR:core.networking_02.Pilot_Station.Pilot_Station-3:ERROR: No function could be found for msg id rpi01_9 with key: WAIT
Traceback (most recent call last):
  File "/home/pi/dev/autopilot/autopilot/core/networking.py", line 597, in handle_listen
    listen_funk = self.listens[msg.key]
KeyError: 'WAIT'

That Line 597 is in class Station, so I think the Station is looking for 'WAIT' in its own listens, but it should be propagating that WAIT to the Net_Node. Or something like that. I'll keep thinking about it.

And sorry, but a follow-up question: will this workaround of using key='CHILD' scale up to the case of a Pilot having multiple Children? If the message is broadcast to all Children, I can just encode the intended target Child in the message payload. But if it is only sent to the first Child, or something like that, then I won't be able to contact the others. If you don't know the answer right away, don't worry, I'll give it a shot soon and see what happens :)

@sneakers-the-rat
Copy link
Contributor

good lord that is sloppy, sorry about that. i'll clean that key mismatch up as well.

that key error is because a station will try to call a listen if the to field matches its id, cleaning this up as well so that the to in the message is always the intended recipient and all the hops are only in the zmq message. a quick and dirty fix would be to try to forward the message on in the case of a KeyError (aka like self.send(msg=msg) .

started working on this in hotfix branch, but have to go cook dinner and don't know if i'll have time afterwards. in any case will have this done by tomorrow!

@sneakers-the-rat
Copy link
Contributor

ty for bearing through this atrocious code, autopilot is striated where the first layer was me learning how to program and now all that has to be replaced now that i actually know how things sort of work... hardware got reinvented, next is the plugin system, then networking... and so on

@sneakers-the-rat
Copy link
Contributor

sneakers-the-rat commented May 25, 2021

have a draft of direct messaging between nodes here: b000504

i'm going to write tests (novel, i know) for the changes and do some godforsaken manual testing to make sure the changes weren't breaking, but it is a simplifying change.

  • Net_Nodes can be given a router_port on init, and if gotten will bind a ROUTER socket to receive messages from networking objects other than the Station that they are 'behind'. Should handle messages the same way as usual, by calling a listen function with a matching key
  • Each net_node still will have only one designated "upstream" node, but since Net_Nodes can make router ports now, that 'upstream' node can be a Net_Node rather than just a Station object. So by default the connections are 'many input, one output'.
  • When sending a message, if the to field is in the senders dict (automatically added on receiving a message from another node), then the router is used, otherwise the previous dealer-only approach is used.
  • If you want to send to multiple recipients (n-to-n) then I think you can do that with router-to-router connections, in which case you would manually call node.router.connect('tcp://ip.address:port') and as long as both of their identities are set (should be) and unique (idk), and you add the bytes encoded id of the recipient to the senders dict (dict so lookups are O(1)) then it should work

the Station objects also had/have a huge amount of extraneous logic in them that I bought for the low, low price of "not having a clear messaging architecture when they were first implemented" and I tried to clean some of that out, so there might be breaking changes that i'll work out over the course of this afternoon, shouldn't take longer than today or tomorrow to finish though.

(oh yeah and i also split up the networking.py file into its own module, as is long overdue for everything in the core module)

@cxrodgers
Copy link
Contributor Author

Wow, thanks for all the hard work! I hope it goes without saying, there's nothing urgent about my request, feel free to prioritize as you see fit.

I didn't fully understand the previous networking model, so I'm not really following your described changes, but when I go back to lab tomorrow I'll work through my use case and I think I'll be able to figure it out, using the info you've provided here. Or, since you're actively developing, I could also wait for the dust to settle a bit. Let me know if there's a particularly good time for me to alpha test some stuff. thanks!

@sneakers-the-rat
Copy link
Contributor

trust me no one understood the previous/current networking model ;) especially looking at it now, really got away from me at some point.

the goal here will be to get the networking modules to a state where they do what you need them to do (and I need them to do for another project) for now, so i'll try to get direct sending between nodes tested and write an example notebook, and then there will be significantly more dust to settle for 0.5.0.

between those points the API will change substantially, but i'm getting better at writing docs for upgrading so the breaking changes will be gentle and no-one will look back reminiscently at the current way.

@sneakers-the-rat
Copy link
Contributor

@cxrodgers
Copy link
Contributor Author

Ok, I've pulled all the new changes from the hotfix branch. I think I just need one last tip.

Here's how I initialize Net_Node on the parent Pilot. Do I need to specify router_port?

        self.node = Net_Node(id="T_{}".format(prefs.get('NAME')),
                             upstream=prefs.get('NAME'),
                             port=prefs.get('MSGPORT'),
                             listens={},
                             instance=False)

By the way, note I had to set instance to False or it crashes with a threading error.

And here's how I initialize Net_Node on the Child.

        self.listens = {
            'WAIT': self.l_wait,
            'PLAY': self.l_play,
            'STOP': self.l_stop,
        }
        self.node = Net_Node('child_pi',
            upstream=prefs.get('NAME'),
            port=prefs.get('MSGPORT'),
            listens=self.listens,
            instance=False,
            )        

What is the correct format for the message send? I've been doing this, from the parent:

        self.node.send(
            to='child_pi',
            key='WAIT',
            value={'foo': 'bar'},
            )

I think it almost works. On the parent Pilot, I see

DEBUG:networking.station_02.Pilot_Station.Pilot_Station-3:FORWARDING (dealer): [b'T_rpi01', b'rpi01', b'child_pi', b'{"flags": {}, "timestamp": "2021-05-26T15:03:48.140249", "ttl": 5, "sender": "T_rpi01", "to": "child_pi", "key": "WAIT", "value": {"foo": "bar"}, "id": "T_rpi01_1"}']

But I don't see anything receive on the child Pi, and it does not enter the self.l_wait() function.

I guess I'm confused about:

  • Do I need to choose a router_port, or just use MSGPORT as before?
  • Is the upstream of a Net_Node just supposed to be prefs.get('NAME'), ie, its own Station? Or is the upstream supposed to be the parent Pi?

@sneakers-the-rat
Copy link
Contributor

sneakers-the-rat commented May 27, 2021

In this case think of each of these nodes as independent of the pilot, nodes you're just using for the task, separate from prefs like MSGPORT which are used by the pilot and terminal's station objects.

The connections are still asymmetrical, where

  • upstream, port, upstream_ip, all configure a (dealer) socket that can initiate a connection to a single, already-bound other (router) socket
  • router_port binds a (router) socket that can be connected to by any number of other (dealer) sockets, but the other sockets have to initiate the connection first.

So the order is important:

  • First, one node needs to bind a router_port, which claims the port and allows other nodes to connect to it.
  • Then, another child node needs to connect to that router_port, where upstream == the id, port == router_port, upstream_ip == ip of the parent node ('localhost' is default, if on the same ip). Specifically for this version, we also need to send a message so the net node object is aware of the connection.
  • After the child connects to the parent's router_port, then the parent can send messages back using that same port -- but it needs to have the child node connect to it first, it can't initiate the connection.

Usually this doesn't matter all that much because zmq is smart and will usually send any messages after the child connects anyway.

To answer your questions specifically:

  • with this change the nodes are connecting directly to each other, so router_port tells the node to bind a new port to receive messages at, it should be different than prefs.get('MSGPORT') (you should get an error because that port should already be bound by the Station object).
  • upstream is rapidly becoming a bad misnomer, it just means the id of the node that it connects to with its dealer socket.

The direction I want to move towards is (hopefully) a much clearer system where every object has a unique ID where you can send messages just to 'agent_id.object_id' and everything else is hidden, so you don't have to mess around manually with ports and ips and etc. this is sort of an intermediate form, where nodes can directly connect to each other without needing to bother with the cumbersome and slow node -> station -> station -> node pattern but you still have to juggle a few IPs and ports. I would recommend maybe adding them as parameters to the task class so you can set them durably without hardcoding.

i'll be sure to include a worked example when i pull this back after testing multihop routing

@sneakers-the-rat
Copy link
Contributor

sneakers-the-rat commented May 27, 2021

so a quick example slightly modified from the test is

from autopilot.networking import Net_Node

def hello(value):
    print(f'hello, {value}')

node_1 = Net_Node(
  id='a', 
  upstream='', port=5000,
  router_port = 5001,
  listens = {'HELLO': hello},
  instance = False
)

node_2 = Net_Node(
  id='b',
  upstream='a',
  port=5001,
  upstream_ip='localhost', # or whatever
  listens = {'HELLO': hello},
  instance = False
)

node_2.send('a', 'HELLO', 'my name is b')
# node_1 should get the message hopefully, 
# and now node_1 can send messages back
# you might need some short sleep to establish the connection if
# you're literally doing them sequentially
node_1.send('b', 'HELLO', 'b, my name is a')

@cxrodgers
Copy link
Contributor Author

It works!! I basically used your example, but the "upstream_ip" is the IP of the parent Pi. I also added a little waiting loop on the parent (which is upstream), so that it didn't begin the task until it had been contacted by each Child. Otherwise, the child's first message to the parent might arrive too late, after the parent had already tried to send a message about the first trial.

In this example, is any traffic actually going over port 5000? Or is port=5000 just specified because port is a required argument? I see that no upstream is needed for node_1, presumably because node_1 is itself the upstream in this application.

Also, when I have multiple Child Pis all connecting to the same Parent, would you recommend using the same Net_Node on the Parent? Alternatively I could have a separate Net_Node to communicate with each Child. For simplicity, I think I'd rather use just the one, as long as it's okay for multiple children to be connecting to the same parent Net_Node on the same router_port.

@sneakers-the-rat
Copy link
Contributor

yesssss glad it works :) feels like magic every time the networking modules actually get a message through

port=5000 just specified because port is a required argument, before the requirement made sense because all Net_Nodes were expected to be 'downstream' of some Station that they sent all messages to, but seems like that can be relaxed.

Yes! totally fine to connect multiple DEALER sockets to one ROUTER socket -- that's the expected pattern i think. After they've sent a message to the parent pi, the send method should automatically use the router to send messages addressed back to the connected children. Currently Net_Nodes only get the value field of the message, so you wouldn't necessarily be able to tell which pi was sending the message unless you put that in the value field. I have thought for awhile that doesn't make a lot of sense, and that all listen types across stations and nodes should just receive the whole message.

@sneakers-the-rat
Copy link
Contributor

sneakers-the-rat commented May 28, 2021

multihop messaging appears to work as well:

https://travis-ci.com/github/wehr-lab/autopilot/builds/227201171
58c1814

see the test here for an example: https://github.com/wehr-lab/autopilot/blob/58c18140f474e7d2560deb8f6dcd74e840534b93/tests/test_networking.py#L107

so there, for a series of networking objects node_1 -> station_1 -> station_2 -> station_3 -> node_3, the message is correctly forwarded when sent like this

node_1.send(to=['station_1', 'station_2', 'station_3', 'node_3'],
                key='GOTIT', value=0)

note that Net_Nodes do not forward messages, they are expected to be the terminal senders/recipients, hence the node to node pattern in previous messages. (again, in the future, my hope is that everything will be p2p <3)

I'm going to do a few more manual tests with the rigs/tasks in our lab just to make qualitatively sure (thank you lack of unit testing, working on it) nothing breaks and then i'll close this and pull back to dev. thanks for raising this :)

@sneakers-the-rat
Copy link
Contributor

alright i'm relatively convinced that the networking fix doesn't break anything. merging now!

sneakers-the-rat added a commit that referenced this issue Jun 1, 2021
Addresses #93 - allows messages to be sent directly between net nodes with less fuss, as well as allows messages to be addressed with a list, see the appropriate tests and the discussion in the linked issue

In addition, fixed a number of things en passant

- added a few tests, including the testing framework to allow GUI tests to be run on the CI, and added tests to the docs experimentally
- send full continuous sound through queue rather than in frames, which was causing the continous sound to become truncated
- pop a dialogue if terminal is launched without any pilots prompting to add a pilot, addressing #27
- better identification of objects in log creation, looking first for an `id` field that will eventually be the uniform way of identifying objects
- split networking into its own module
- more type hinting
- continued work on removing ad-hoc logic, failing loudly instead of trying to do things implicitly
- added favicon to docs <3
- refactored docs into subfolders rather than long period-delimited filenames.
- bumped zmq and tornado dep versions


Squashed commits: 


* travis basics

* travis test 2

* travis test 3 - no enc setup scripts in setup.py

* travis test 4 - get the command right

* named test file right

* reversing test of event invoker

* jesuz

* drop all chunks at once

* debug flag

* handling emptiness and making logger from process

* fixing multihop messages... just the send part, now need to fix the 'handle listen' in  both the Station and the Net_Node
also some type hinting and docstring fixes and cleanup as we go

* refactoring networking to own module
refactoring docs to use folders instead of extremely long paths

* image links

* api links

* favicon

* somehow still missing networking docs

* favicon

* actually adding scripts

* draft of direct messaging between net_nodes

* networking docs

* clean up station object
- absolutely had no idea what class attributes were when i made this i guess
- fixing docstring
- type hinting
- spawning thread in the process by putting it in run rather than in init

* correct type hints

* maybe get scripts to render?

* node-to-node test

* multihop test
- station objects and node objects count how many messages they receive
- let station objects be instantiated programmatically
- fixing programmatic station release
- loggers check id first before name

* fixing prefs test defaults

* coverage of mp?

* coverage of mp?

* coverage of mp?

* coverage of mp?

* coverage of mp?

* coverage of mp?

* testing delay in sound trigger

* testing delay in sound trigger

* set agent
clear method for prefs

* the godforsaken managers module

* the godforsaken managers module

* the godforsaken managers module

* - terminal test
- get app from QApplication.instance() instead of implicitly
- close ioloop on release of net nodes
- 'float' type in setup
- don't save prefs if in tests
- bumping requirements to support some features we rely on

* asyncio debug mode

* do it except not in different build configs

* do it except not in different build configs

* close event?

* istg

* again istg

* again istg

* graspin at straws

* graspin at straws

* good heavens finally.

reverting exploratory attempts at fixes

* adding tests to the docs

* prompt to add pilots on blank screen init
sneakers-the-rat added a commit that referenced this issue Aug 4, 2021
OK i am too fried to write the PR description, and there's still a bit to do here, but i want to start hoisting this massive thing onto the table. one day we will do this in a way that isn't rewriting the whole thing between versions, but it is not this day.


v0.4.0 - Become Multifarious (July 30, 2021)
------------------------------------------------------

This release is primarily to introduce the new plugin system, the autopilot wiki, and their integration as a way of
starting the transformation of Autopilot into a tool with decentralized development and governance (as well as
make using the tool a whole lot easier and more powerful).

*With humble thanks to Lucas Ott, Tillie Morris,* `Chris Rodgers <https://github.com/cxrodgers/>`_,
`Arne Meyer <https://github.com/arnefmeyer>`_ , `Mikkel Roald-Arbøl <https://github.com/roaldarbol>`_ ,
`David Robbe <https://github.com/neurodavidus>`_ ,
*and an anonymous discussion board poster for being part of this release.*

New Features
~~~~~~~~~~~~~

* `Registries & Plugins <https://github.com/wehr-lab/autopilot/pull/109>`_ - Autopilot now supports users writing their code
  outside of the library as plugins! To support this, a registry system was implemented throughout the program. Plugin objects
  can be developed as objects that inherit from the Autopilot object tree -- eg. implementing a GPIO object by subclassing
  :class:`.hardware.gpio.GPIO` , or a new task by subclassing :class:`~.tasks.task.Task` . This system is flexible enough
  to allow any lineage of objects to be included as a plugin -- stimuli, tasks, and so on -- and we will be working to
  expand registries to every object in Autopilot, including the ability for plugins to replace core modules to make
  Autopilot's flexibility verge on ludicrous. The basic syntax of the registry system is simple and doesn't require any
  additional logic beyond inheritance to be implemented on plugin objects -- ``autopilot.get('object_type', 'object_name')``
  is the basic method, with a few aliases for specific object types like ``autopilot.get_hardware()``. Also thanks to
  `Arne Meyer <https://github.com/arnefmeyer>`_ for submitting an early draft of the registry system and
  `Mikkel Roald-Arbøl <https://github.com/roaldarbol>`_ for raising the issue.
* At long last, the Autopilot Wiki is alive!!!! - https://wiki.auto-pi-lot.com/ - The wiki is the place for communal
  preservation of technical knowledge about using Autopilot, like hardware designs, build guides, parameter sets,
  and beyond! This isn't any ordinary wiki, though, we got ourselves a *semantic wiki* which augments traditional wikis
  with a rich system of human and computer-readable linked attributes: a particular type of page will have some set of attributes,
  like a page about a 3D printed part will have an associated .stl file, but rather than having these be in plaintext
  they are specified in a format that is queryable, extensible, and infinitely mutable. The vision for the wiki is much
  grander (but not speculative! very concrete!) than just a place to take notes, but is intended to blend the use of
  Autopilot as an experimental tool with body of knowledge that supports it. Autopilot can query the wiki with the ``wiki`` module
  like ``wiki.ask('[[Category:3D_CAD]]', 'Has STL')`` to get links to all .stl files for all 3D parts on the wiki. The integration
  between the two makes using and submitting information trivial, but *also* makes *designing whole new types of community interfaces*
  completely trivial. As a first pass, the Wiki will be the place to index plugins, the system for submitting them, querying them,
  and downloading them only took a few hours and few dozen lines of code to implement. The wiki is infinitely malleable -- that's the point --
  and I am very excited to see how people use it.
* Tests & Continuous Integration with Travis! We are on the board with having nonzero tests! The travis page is here: https://travis-ci.com/github/wehr-lab/autopilot
  and the coveralls page is here: https://coveralls.io/github/wehr-lab/autopilot .  At the moment we have a whopping 27% coverage,
  but as we build out our testing suite we hope that it will become much easier for people to contribute to Autopilot and be
  confident that it works!
* New Hardware Objects
    * :class:`.cameras.PiCamera` - A fast interface to the PiCamera, wrapping the picamera library, and using tips from its developer to juice every bit of speed i could!
    * The I2C_9DOF object was massively improved to take better advantage of its onboard DSP and expose more of its i2c commands.
* New Transforms
    * :class:`.timeseries.Kalman` - adapted a Kalman filter from the wonderful filterpy package! it's in the new timeseries transform module
    * :class:`.geometry.IMU_Orientation` - IMU_Orientation performs a sensor fusion algorithm with the Kalman Filter class to combine gyroscope and accelerometer measurements into a better estimate of earth-centric roll and pitch.
      This is used by the IMU class, but is made independent so it can be used without an Autopilot hardware object/post-facto/etc.
    * :class:`.timeseries.Filter_IIR` - Filter_IIR implements scipy's IIR filter as a transform object.
    * :class:`.timeseries.Integrate` - Integrate adds successive numbers together (scaled by dt if requested). not much by itself, but when used with a kalman filter very useful :)
    * :class:`.geometry.Rotate` - use scipy to rotate a vector by some angle in x, y, and/or z
    * :class:`.geometry.Spheroid` - fit and transform 3d coordinates according to some spheroid - used in the IMU's accelerometer calibration method: given some target spheroid, and some deformed spheroid (eg. a miscalibrated accelerometer might have the x, y, or z axis scaled or offset) either explicitly set or estimated from a series of point measurements, transform future input given that transformation to correct for the deformed source spheroid.
* New Prefs
    * ``'AUTOPLUGIN'`` - Attempt to import the contents of the plugin directory,
    * ``'PLUGIN_DB'`` - filename to use for the .json plugin_db that keeps track of installed plugins',
    * ``'PING_INTERVAL'`` - How many seconds should pilots wait in between pinging the Terminal?',
    * ``'TERMINAL_SETTINGS_FN'`` - filename to store QSettings file for Terminal',
    * ``'TERMINAL_WINSIZE_BEHAVIOR'`` - Strategy for resizing terminal window on opening',
    * ``'TERMINAL_CUSTOM_SIZE'`` - Custom size for window, specified as [px from left, px from top, width, height]',

Major Improvements
~~~~~~~~~~~~~~~~~~~

* Stereo Sound (Thank you `Chris Rodgers! <https://github.com/cxrodgers/>`_) - #102
* Multihop messages & direct messaging - #99 - it is now possible to
  send multihop messages through multiple Station objects, as well as easier to send messages directly
  between net nodes. See the examples in the network tests section of the docs.
* Multiple Children  (Thank you `Chris Rodgers! <https://github.com/cxrodgers/>`_) - #103 -
  the ``CHILDID`` field now accepts a list, allowing a Pilot to initialize child tasks on multiple children. (this syntax and
  the hierarchical nature of pilots and children will be deprecated as we refactor the networking modules into a general mesh system,
  but this is lovely to have for now :)
* Programmatic Setup - #33 - noninteractive setup of prefs and
  scripts by using ``autopilot.setup -f prefs.json -p PREFNAME=VALUE -s scriptname1 -s scriptname2``
* Widget to stream video, en route to more widgets for direct GUI control of hardware objects connected to pilots
* Support python 3.8 and 3.9 essentially by not insisting that the spinnaker SDK be installable by all users (which at the time
  was only available for 3.7)


Minor Improvements
~~~~~~~~~~~~~~~~~~~

* Terminal can be opened maximized, or have its size and position set explicitly, preserve between launches (Thank you `Chris Rodgers! <https://github.com/cxrodgers/>`_) - #70
* Pilots will periodically ping the Terminal again, Terminal can manually ping Pilots that may have gone silent - #91
* Pilots share their prefs with the Terminal in their initial handshake - #91
* Reintroduce router ports for net-nodes to allow them to bind a port to receive messages - 35be5d6
* Listen methods are now optional for net_nodes
* Allowed the creation of dataless tasks - 628e1fb
* Allowed the creation of plotless tasks - 08d99d5
* The ``I2C_9DOF`` clas uses memoryviews rather than buffers for a small performance boost - 890f2c5
* Phasing out using ``Queue`` s in favor of ``collections.deque`` for applications that only need thread and not process safety because they
  are way faster and what we wanted in the first place anyway.
* New Scripts - ``i2c``, ``picamera``, ``env_terminal``
* utils.NumpyEncoder and decoder to allow numpy arrays to be json serialized
* calibrations are now loaded by hardware objects themselves instead of the extraordinarily convoluted system in ``prefs`` -- though
  some zombie code still remains there.
* Net nodes know their ip now, but this is a lateral improvement pending a reworking of the networking modules.
* ``performance`` script now sets ``swappiness = 10`` to discourage the use of swapfiles - see https://www.raspberrypi.org/forums/viewtopic.php?t=198765
* Setting a string in the ``deprecation`` field of a pref in ``_DEFAULTS`` prints it as a warning to start actually deprecating responsibly.
* Logging in more places like Subject creation, manipulation, protocol assignation.

Bugfixes
~~~~~~~~

* Loggers would only work for the last object that was instantiated, which was really embarassing. fixed - #91
* Graduation criteria were calculated incorrectly when subjects were demoted in stages of a protocol - #91
* fix durations in solenoid class (Thank you `Chris Rodgers! <https://github.com/cxrodgers/>`_) - #63
* LED_RGB ignores zero - #98
* Fix batch assignment window crashing when there are subjects that are unassigned to a task - e42fc58
* Catch malformed protocols in batch assignment widget - 2cc8508
* Remove broken ``Terminal.reset_ui`` method and made control panel better at adding/removing pilots - #91
* Subject class handles unexpected state a lot better (eg. no task assigned, no step assigned, tasks with no data.) but is still
  an absolute travesty that needs to be refactored badly.
* The jackclient would crash with long-running continuous sounds as the thread feeding it samples eventually hiccuped.
  Made more robust by having jackclient store samples locally int he sound server rather than being continuously streamed from the queue.
* PySide2 references still incorrectly used ``QtGui`` rather than ``QtWidgets``
* pigpio scripts would not be stopped and removed when a task was stopped, the :func:`.gpio.clear_scripts` function now handles that.
* ``xcb`` was removed from ``PySide2`` distributions, so it's now listed in the requirements for the Terminal and made available in the ``env_terminal`` script.
* ``LED_RGB`` didn't appropriately raise a ``ValueError`` when called with a single ``pin`` - #117
* A fistful of lingering Python 2 artifacts

Code Structure
~~~~~~~~~~~~~~~

* continuing to split out modules in :mod:`autopilot.core` - networking this time
* utils is now a separate module instead of being in multiple places
* the npyscreen forms in ``setup_autopilot`` were moved to a separate module
* ``setup_autopilot`` was broken into functions instead of a very long and impenetrable script. still a bit of cleaning to do there.
* ``autopilot.setup.setup_autopilot`` was always extremely awkward, so it's now been aliased as ``autopilot.setup``
* the docs have now been split into subfolders rather than period separated names to make urls nicer -- eg /dev/hardware/cameras.htm
  rather than /dev/hardware.cameras.html . this should break some links when switching between versions on readthedocs
  but other than that be nondestructive.

Docs
~~~~

* new :ref:`quickstart` documentation with lots of quick examples!

Regressions
~~~~~~~~~~~

* Removed the ``check_compatible`` method in the Transforms class. We will want to make a call at some point if we want to implement a full realtime pipelining framework or if we want to use something like luigi or joblib or etc.
  for now this is an admission that type and shape checking was never really implemented but it does raise some exceptions sometimes.

## todo before merge

- [x] changelog
- [x] write plugins docs
- [x] write wiki docs
- [x] finish uploading initial round of information to wiki
- [x] refresh user guide docs
- [x] remove lab-specific code.



* platform driver skeleton

* formatting

* formatting

* note on processing

* testing docs

* checkpoint actually think i'm misreading the basic program

* mask method implemented with columnwise bit banging

* a lil more documentation and type annotation

* how 2 document props?

* how 2 document props?

* draft height prop

* bugfixing plat

* lol compare the variable not the literal number 2

* absolute value

* clean init

* fliplr binary col command

* doin it manual style

* copy instead of replace

* testing faster move speed

* release method, default times

* join method

* draft absolute position movement mode

* level method

* lower than max for now

* lower than max for now

* lower than max for now

* maybe loading variable wrong?

* ohhh its the mask lol

* remeasured offsets

* remeasured offsets

* handle setting height with int when mask is all zeros
implement __getitem__ and __setitem__ for setting height.

* update docs, include examples

* oop forgot about how sensitive sphinx was to headers

* need linebreaks, also giving example header a little space

* omg sphinx

* omg sphinx

* logging in subject creation

* more logging flags....
fixing unraised exceptions in subject
handling exceptions in graduation init
logger in subject

* handle unassigned subjects in batch reassign, various other bugfixes and cleanups int he godforsaken gui module

* raise exception with informative error if prepare_run called without current

* correct check for presence of trial table
mute annoying lack of base sound class, be noisy if it fails instead.

* provisionally allow dataless tasks...

* allow tasks without plots

* Update gui.py

crude hack to get the QtWdigets for calibrate ports working by commenting out QFont stuff

* catch malformed protocol files in batch reassign

* removing extraneous spaces; adding a few comments

* comments

* qt deprecations: replacing desktop with primaryScreen and screenGeometry with size

* comments

* removing commented code about logo

* remove the subtractions, because this is all captured by availableGeometry anyway

* implementing new pref TERMINAL_WINSIZE_BEHAVIOR

* bugfix utils.py
mike

* index error when assigning height with int

* velocity mode

* can't have negative params

* negative values of velocity, and make 0 velocity init stick

* pigpio by asking for permission instead of checking agent in i2c

* start pigpiod

* buffer -> memoryview
script to enable i2c

* - don't erase private variables on init
- use numpy instead of struct

* squeeze extra dims

* IIR Filter transform

* add to docs

* actually add to docs...

* jesus christ get the name right

* correct attributes

* testing references

* bibtex not biblatex

* pitch and roll

* implementing kalman filter and orientation transform... doing some testing.

* uysing degrees again, setting default gyro scale

* Let terminal GUI remember its previous size (depending on a new pref) (#68)

* removing extraneous spaces; adding a few comments

* comments

* qt deprecations: replacing desktop with primaryScreen and screenGeometry with size

* comments

* removing commented code about logo

* remove the subtractions, because this is all captured by availableGeometry anyway

* implementing new pref TERMINAL_WINSIZE_BEHAVIOR

* Revert "Let terminal GUI remember its previous size (depending on a new pref) (#68)"

This reverts commit eda5404.

* minor thangs

- cleaned up docstring a little bit
- switched location of terminal settings to 'TERMINAL_SETETINGS_FN'
- added 'custom' size mode
- made 'TERMINAL_WINSIZE_BEHAVIOR', a 'choice' box, and 'remember' is the default
- fixed resizing of Control Panel widgets so they fill the available space

* play nicely on cli, exit with 1 if scripts weren't successful

* set gyro HPF

* draft orientation kalman filter wrapper
- change to [roll, pitch]

* bugfixin'

* bugfixin'

* bugfixin'

* bugfixin'

* bugfixin'

* bugfixin'

* not just deleting every entry

* not just deleting every entry

* add flag to invert gyro measaurements

* adding scripts to docs

* refactor imu processor and 9dof
- build in kalman filtration into rotation attr
- make imu orientation class more flexible (implements the general trig but also the kalman filter)
- keep last readings
- set i2c baudrate to 100kHz in i2c script

* extra dimension

* start implementing rotation, adding spec for accel range

* rotate class

* correct comparison

* correct comparison

* correct comparison

* flipping gyroscope polarity

* eye instead of ones...

* lower HPF, increase accel range

* read separately?

* integrate and add

* don't require format in and out until they like work lol

* reinstate route port, make lsitens optional

* reinstate route port, make lsitens optional

* log history changes in subject

* spheroid transformer and tests

also
- numpy encoder for serializing to json
- start of calibration logic in hardware classes

* travis???

* an example

* calibration routine for accelerometer

* gathering info for picamera class
add picamera script

* PiCamera draft!!!

* docs ref fixes

* ig picamera doesn't use latest on rtd

* starting to write parallax task

* making video widget to stream frames to while positioning/configuring/training dlc
also fixing old QtGui calls
- !!! and sending prefs from pilot with handshake method

* catch errors on creating batch reassign window

- remade pop_dialogue function to use pyside2 objects, support window modality
- started to gather shorthand references to GUI objects in _MAPS object...

good lord the GUI needs to be rearchitected.

* patches for subject...
- record step in assign_protocol, rather than in the terminal, as was done for some reason before
- fix get_step_history which would get hung on a unicode error
- filter data given to graduation objects such that data from previous runs on a particular step are removed -- eg. if the subject was pushed back to do a step again, don't give data from the previous time the subject did the step to assess graduation criteria or else it would always graduate immediately.

* starting to work with pi

* fix add new pilot in gui
fix pinging

* video streaming draft

* video streaming draft

* video streaming draft

* video streaming draft

* video streaming draft

* video streaming draft

* fixing autograd actually

* really actually fixing autograduation by actually slicing the table to exclude trials from after the reassignment window
- don't overwrite grad params if subject has overlapping params
- cleaned up old python 2 relics & some unnecessary whitespace & imports

* fixing pi state calls and start button updating
- fixing add new pilot panel when new pilots signal their existence
- actually storing and transmitting state correctly
- creating pilots in pilot_db

* reimplementing continous sounds as something that jackd holds locally as a cycle rather than as a thread that feeds jack server.

* reimplementing continous sounds as something that jackd holds locally as a cycle rather than as a thread that feeds jack server.

* start button can ping pis

* pilots ping

* add closeEvent to video streamer
- try to fix logging from multiple sources
- don't send continuous data to plots if they don't exist
- remove socket.sending check from single message streams
- make default min_size in stream video == 1
- handle full queues in camera class

* trying deque instead of queue

* trying deque instead of queue

* trying deque instead of queue

* nocopy on send

* implement grayscale

* implementing fast grayscale capture

* implementing fast grayscale capture

* dq

* fixing online graduation
giving graduation objects loggers

* Fixing logging from multiple objects in the same logger
- instead of giving the file handler to the individual logger that we create, eg. "module.name.Class_Name.id", we need to give it to the parent module-level logger object, so that logging messages from children are passed up and all use it. as is, only the first logger for a given module actually logs.
- if the log directory doesn't exist, try to create it.

* writing video
camera rotation
moving big comment block in gui to scaprs lol

* bugfixin

* bugfixin

* fix pilot db update
picamera release
pyqtgraph image axis order

* - clear scripts when stopping task
- put task end method in 'finally' block
- add 'swappiness = 10' to performance script

* linebreak that didn't make it?

* dont raise warnings lmao

* fix pinging

* fix bug where LED pins could not be set to zero (evaluates False)

* Hotfix - direct messaging between nodes & multihop messages (#99)

Addresses #93 - allows messages to be sent directly between net nodes with less fuss, as well as allows messages to be addressed with a list, see the appropriate tests and the discussion in the linked issue

In addition, fixed a number of things en passant

- added a few tests, including the testing framework to allow GUI tests to be run on the CI, and added tests to the docs experimentally
- send full continuous sound through queue rather than in frames, which was causing the continous sound to become truncated
- pop a dialogue if terminal is launched without any pilots prompting to add a pilot, addressing #27
- better identification of objects in log creation, looking first for an `id` field that will eventually be the uniform way of identifying objects
- split networking into its own module
- more type hinting
- continued work on removing ad-hoc logic, failing loudly instead of trying to do things implicitly
- added favicon to docs <3
- refactored docs into subfolders rather than long period-delimited filenames.
- bumped zmq and tornado dep versions


Squashed commits: 


* travis basics

* travis test 2

* travis test 3 - no enc setup scripts in setup.py

* travis test 4 - get the command right

* named test file right

* reversing test of event invoker

* jesuz

* drop all chunks at once

* debug flag

* handling emptiness and making logger from process

* fixing multihop messages... just the send part, now need to fix the 'handle listen' in  both the Station and the Net_Node
also some type hinting and docstring fixes and cleanup as we go

* refactoring networking to own module
refactoring docs to use folders instead of extremely long paths

* image links

* api links

* favicon

* somehow still missing networking docs

* favicon

* actually adding scripts

* draft of direct messaging between net_nodes

* networking docs

* clean up station object
- absolutely had no idea what class attributes were when i made this i guess
- fixing docstring
- type hinting
- spawning thread in the process by putting it in run rather than in init

* correct type hints

* maybe get scripts to render?

* node-to-node test

* multihop test
- station objects and node objects count how many messages they receive
- let station objects be instantiated programmatically
- fixing programmatic station release
- loggers check id first before name

* fixing prefs test defaults

* coverage of mp?

* coverage of mp?

* coverage of mp?

* coverage of mp?

* coverage of mp?

* coverage of mp?

* testing delay in sound trigger

* testing delay in sound trigger

* set agent
clear method for prefs

* the godforsaken managers module

* the godforsaken managers module

* the godforsaken managers module

* - terminal test
- get app from QApplication.instance() instead of implicitly
- close ioloop on release of net nodes
- 'float' type in setup
- don't save prefs if in tests
- bumping requirements to support some features we rely on

* asyncio debug mode

* do it except not in different build configs

* do it except not in different build configs

* close event?

* istg

* again istg

* again istg

* graspin at straws

* graspin at straws

* good heavens finally.

reverting exploratory attempts at fixes

* adding tests to the docs

* prompt to add pilots on blank screen init

* postmerge node fixes

* rough draft of parallax task

(and give ip property to nodes)

* change explicit type check to catchall text box that should just fail if malformed in setup_autopilot.py, since will be coerced with ast.literal_eval anyway.

* specific pytest-qt

* imgconverter docs fix?

* imgconverter docs fix?

* fixing lots of things in the docs and hopefully pdf building

* seems to be a problem with blink...

* really?

* testing whether its the blink

* making links absolute

* see if absolute fixes

* Implement Registries, Plugins, and Wiki Integration (#109)

Very late now, but I believe i have done this: #39

I will return tomorrow to write the docs and the PR, but yes it is time.

dump anything that inherits from the appropriate metaclass into `PLUGINDIR` and...

```python
import autopilot

# list all hardware
autopilot.get('hardware')
# or an alias
autopilot.get_hardware()

# get hardware (including from plugins)
autopilot.get('hardware', 'PWM')

# get anything
autopilot.get('task')
autopilot.get('graduation')
autopilot.get('transform')
autopilot.get('children')
```

and as a little bonus...

```python
>>> from autopilot.utils import wiki
>>> wiki.ask('[[HiFiBerry Amp2]]', "Uses GPIO Pin")
[{'Uses GPIO Pin': [3, 5, 7, 12, 35, 38, 40],
  'name': 'HiFiBerry Amp2',
  'url': 'https://wiki.auto-pi-lot.com/index.php/HiFiBerry_Amp2'}]
```

which will serve as the means of submitting plugins and doing some stuff that is truly messed up how cool it is... more soon ;)

* deduplicate utils

formerly had core/utils and a utils module, just have the module now.

* draft simpler registry :)

* moving common utility functions out of __init__

* task and hardware aliases

* proper sorting

* got excited and made a wiki API access module in this branch by accident whoops

* want to get one good build off!

* want to get one good build off!

* tests for registry module

* whoops reverting stuff that was broken at the time of adding it lmao jonny watch the results of the tests jesus christ

* basic test of a plugin

* implementing get throughout rest of library

removing imports and references to hardware and task objects, and finally killed the godforsaken Task List once and for all.
also adding other assorted improvements and bugfixes from the registries branch

* just for shits lets see if other versions work

* fix path traversal through modules whose base class is not in __init__

* proof of concept plugins manager ;)

* add xcb depends to scripts and docs to close #108

* fix: set_duration and Solenoid.duration are both in ms, no conversion from sec necessary (#63)

* Connect to multiple CHILDID (#103)

* station.l_child can now send START to multiple CHILDID

* update prefs to indicate that CHILDID can now be a list

* typo

* Stereo sound (#102)

From @cxrodgers 👍 

This is a PR for issue #94

This implements multi-channel (e.g., stereo) sound. 

Some major changes:
* The sound classes in autopilot.stim.sound accept a `channel` argument. (Right now, only Noise does this.) If this is `None` (the default), a mono (1-dimensional) sound is produced, exactly as before. If this is 0, a Noise burst only in the first channel is produced. If 1, a Noise burst only in the second channel is produced.
* Multi-channel sounds are properly padded to the correct chunk length in Jack_Sound.chunk (implemented here: https://github.com/cxrodgers/autopilot/blob/4dfe9e77687a8a9083ee7903136f99cc7e63671f/autopilot/stim/sound/sounds.py#L223)
* The preference OUTCHANNELS is now interpreted differently. If empty string, then plays in mono mode (same sound to all speakers). If a list, connect channels to physical speakers in that order. If an int, treat as a list of length one. (implemented here: https://github.com/cxrodgers/autopilot/blob/4dfe9e77687a8a9083ee7903136f99cc7e63671f/autopilot/stim/sound/jackclient.py#L160)
* I removed padding code in the process() function because 1) this should already be done by Jack_Sound.chunk; 2) process probably doesn't have time to do this kind of thing; 3) the padding code was causing errors on multi-channel sounds, would need to be reimplemented as in Jack_Sound.chunk, but see reason number 1.
* OUTCHANNELS default in prefs.py changes to empty string (mono mode, no changes to Task code needed). The old default of [1] suggested 1-based indexing and in any case produced essentially mono results.
* NCHANNELS deprecated in prefs.py


Error are raised if:
* The length of OUTCHANNELS is longer than the number of available ports
* OUTCHANNELS indicates mono mode, but multi-channel audio is provided
* OUTCHANNELS is length N, but M-channel audio is provided, where N != M. However, 1-dimensional (mono) audio can be provided, and this will play to all N speakers.

jonny had suggested using None as the OUTCHANNELS flag for "connect no speakers", but [] works for this more naturally. Also, None cannot easily be put into the json.

Known issues:
* Presently only Noise can produce multi-channel output. Others can only produce mono output, as before. Should be an easy improvement.
* Only works for Jack, not pyo! 

Not tested!
* pyo is not tested!
* AUDIOSERVER = 'docs' is not tested!
* continuous mode is not tested!

Tested on:
* jackd from APT and (less thoroughly) jackd built from source

I know this is a big change, please lmk if changes are requested/needed!

* first pass at stereo

* doc; remove extra whitespace; reorganize order of imports

* doc Noise

* doc and reorganize the way Noise is generated

* fix typo

* intify channel if possible

* remove duplicate outports code

* typo

* handle stereo or mono output

* new way of init outports

* remove empty lines and comments

* doc

* move soudn writing code to its own method

* cherry pick a commit to init stereo_output, which required a merge on init_logger

* fix chunking and padding for multi-channel sound

* removing debug statements

* reimplement mono sound

* fix typo; error messages; use empty string instead of None as mono flag

* remove comment

* change default of OUTCHANNELS to empty string (mono)

* deprecate NCHANNELS

* adding test_sound.py

* catch ModuleNotFoundError

* server_type is not accessible via self

* trace out FS; finish test_init_noise

* put various durations into test function

* flesh out mono and stereo tests

* comments

* change calling functions to pytest parameterizations

* deprecation for prefs

* replace SOUND_LIST with autopilot.get, make placeholder Stim metaclass to make it work when audio server is undefined

* fix registry with plugin present

* warn if jackd server sampling rate doesn't match prefs

Co-authored-by: sneakers-the-rat <JLSaunders987@gmail.com>

* add .bib for docs

* moving numpy encoder and decoder to new utils location

* move parallax to plugin repo:
https://github.com/auto-pi-lot/autopilot-plugin-parallax

* [skip travis] cleaning up some more code and docs from parallax

* [skip travis] cleaning up dead files

* Programmatic setup!

* cleaned up the setup_autopilot routine a lot. split everything into separate functions so at least the behavior is *somewhat* encapsulated. they're still very messy and this needs some real reconsideration because it's pretty fragile, but hey at least now it can be called as something other than a module from __main__
* Programmatic setup with custom prefs (specified individually as -p PREFNAME=VALUE -p PREF2=VAL2 and -f prefs.json) and scripts.
* allowed passing multiple scripts and prefs
* still need to write tests

* actually propagate custom directory passed with -d/--dir

basically need to make sure the rest of the directory structure underneath it follows the basic one. implemented by just making the prefs dictionary explicitly and adding it into the prefs that are passed to the setup form (rather than trying to make some auto-re-computing thing)

* whoa i cant believe i actually just relied on a global variable.

* [skip travis] v0.4.0 changelog

* [skip travis] credits in changelog and some formatting

* [skip travis] grats

* maybe try actually knowing css

* [skip travis] getting started refreshing docs

* raise valueerror when instantiating LED_RGB with malformed `pins` or when `pin` is given

* [skip travis] - quickstart checkpoint

* [skip travis] - quickstart checkpoint

* [skip travis] - quickstart checkpoint

* quickstart

- state property for GPIO
- some more params for Transformer to let it be used with the new mode of networking
-

* [skip travis] fix chris' name

* [skip travis] clearing up a lot of erroneous documentation about hardware class creation

* [skip travis] trying to include raw blink

* making textual blink

* [skip travis] checking nbsp

* [skip travis] checking nbsp

* [skip travis] last bits for the night

* [skip travis] last bits for the night

* [skip travis] committing before battery runs out!

* [skip travis] committing before battery runs out!

* [skip travis] plugins docs!

* removing wehrlab specific stuff
- changing Nafc class references to use registries

* add tests

* bump version to v0.4.0!!!!

and update what's new ;)

Co-authored-by: Mike Wehr <wehr@uoregon.edu>
Co-authored-by: Chris Rodgers <xrodgers@gmail.com>
Co-authored-by: sneakers-the-rat <j@nny.fyi>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants