Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

Add serial protocol #59

Merged
merged 39 commits into from
Apr 8, 2016
Merged

Add serial protocol #59

merged 39 commits into from
Apr 8, 2016

Conversation

ed7coyne
Copy link
Collaborator

@ed7coyne ed7coyne commented Feb 3, 2016

Add serial protocol for Firebase "modem"

Related #35.

>>GET /user/aturing
<<39 { "first" : "Alan", "last" : "Turing" }

##GET_VALUE
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@proppy
First place that might be controversial. I think it is important to not force the user to parse json. I would almost be inclined to not include the primary "get" at all. But I could see it as useful for some users.

I feel that a goal of this project is to provide cloud backends for non-cloud users who have sofar stayed local or offline due to a reluctance to face the learning curve of cloud tech. json isn't a common protocol in the embedded world and is relatively hard/expensive to decode.

##INIT
Must be called after creating a Serial connection, it can take either just a host for accessing public variables or you may also provide a secret for accessing protected variables in the database.
###Usage
INIT $Host
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could have that be FIREBASE $HOST $SECRET

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm not sure about that one, INIT implies that it needs to be called everytime to initialize the connection.

Maybe CONNECT $HOST $SECRET?

Just It seems these should all be commands to the system and phrased as such where FIREBASE isn't really a command.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that if the CHIPLET was programmed with multiple APIs, it might expose similar commands derived from REST verbs over different APIs (hence the explicit 'FIREBASE').

But that may be a premature discussion, what about 'BEGIN' to make it Arduinoish?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah got ya. Hadn't considered the other APIs. BEGIN works too, we can do BEGIN_FIREBASE if we want to leave the option open for other APIs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do only begin for now ;) I agree we should worry about multiple API when we have more than 1 binding ;) sorry for the disgression.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then what about:

API firebase version
BEGIN someurl

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not really sold on adding an extra call. I think the fewer calls you need to make to get the env setup to start communicating the better.

I guess for versions I would just change the begin call. i.e. create a BEGIN_FIREBASE2 if we ever need to implement a breaking change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I liked about the API prelude is that it makes it feels declarative. Like an #include or an import. Later we might add some extra calls that allow you to manage the lifecycle of the APIs loaded in your chiplet: list version, OTA, etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright I am on board with something like that. We can have a concept of "default api" that will load at startup.

So for now we can just leave this out all together and address it later on when we introduce other APIs or versions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright I am on board with something like that. We can have a concept of "default api" that will load at startup.

Yes, so for now I think it's fine to just have BEGIN url

@proppy
Copy link
Contributor

proppy commented Feb 3, 2016

/cc @aliafshar

remove GET_VALUE
$DATA_BYTE_COUNT $DATA
###Examples
>>GET_VALUE /user/aturing/first
<<4 Alan
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another issue with having the size as TEXT is that is also a variable length unless we do some padding.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true but you can just read until whitespace. there is also Serial.parseInt()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then what's the improvement over reading until \n? That you're guaranteed the size will never cross a given threshold (a few digits).

Maybe this could come in the same kind of stateful MODEs than type prefix and forced json strings?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you know on the outset the size of the value coming back you can prepare for it. For example you can allocate a buffer large enough to hold it without having to allocate a smaller buffer and grow it. You can also quickly decide if something is too large to process and close the connection.

We could also return text without the json escaping which might be nice so the user doesn't need to unescape it themselves. For example if they wanted to send it directly to a display.


##GET_BULK
Same as GET but returns value with size prefix. useful when value is expected to be large so you can know the size before accepting value.
Also only returns values at leaf nodes, if called on internal node returns error.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that the bulk MODE would return the data always as it is coming from fbase (json escaped), otherwise it's a strict subset of GET behavior.

Maybe we need separate flag/mode for json/size/type and keep the default very simple and readable (leaf value, un escaped, no prefix), wdyt?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am ok with just dropping the bulk mode for now. We can add it back if we see a need for it in practice.
I do think the return from GET needs a prefix though, to allow reliable testing to see if it is an error or valid response before you try to parse it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love if we added all of those as optional stateful flags you could turn on and off so that we can experiment with the wire protocol and see how it feels without adding all the different combination as separate commands.

Then we can debate which one is the default, but everything you listed sounds nice to have:

  • type prefix
  • size prefix
  • decoded leaf value
  • disabling non leaf value

@proppy
Copy link
Contributor

proppy commented Feb 4, 2016

After thinking more about it I think you can disregard most of my comments wrt to optional type prefix.

It would introduce ambiguities in the protocol to make type prefix optional if we unescape leaf values: client won't be able to distinguish an error from a string value that start with !, or a number value from a string with a number.

@proppy
Copy link
Contributor

proppy commented Feb 4, 2016

I also really like the Redis protocol doc you linked http://redis.io/topics/protocol.

I think most of it is directly applicable here and we could easily derive from it with a few caveats:

  • we don't really have arrays
  • number can be float
  • we also have booleans
  • leaf simple strings and numbers are not size prefixed only bulk json values are.

##Response Format Byte
All responses will be prefixed with one of the following bytes signifying the response type.
```
+ If response is ok and a raw value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also have something for raw number and booleans ( maybe : and ? ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

+ If response is ok and a raw string value.
* If response is ok and a raw string value prefixed by count of bytes in response then new line.
# If response is ok and a integer value.
. If response is ok and a float value.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json doesn't have a difference between float and int: so maybe just :number

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json doesn't but I think there is a lot of value in providing the distinction for c++ parsing. So you can do a atoi() call with confidence knowing that the string will parse. I think we can determine pretty easily in the library (check for a '.').

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be down for always return float, so that we can always use atofon the receiving size.

We never know if something is intended to be stored as an int, or as a float on the firebase side: it might just be that the current intermediate value is a round number. It seems easy enough for the client to cast it back to an int when needed (or maybe we could expose a typed GET that always return int).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see where that could be a problem if the value changes from float to int due to chance. Alright I am onboard with leaving the ambiguity up to the user to resolve. Just label them a "number", the user will probably know with domain context what to expect.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's why I think we might just need : for number and just return what we get from firebase.

@ed7coyne
Copy link
Collaborator Author

I think we have reached consensus here. Are there any more outstanding issues or does this protocol look like a solid starting point?

###Usage
GET $PATH
###Response
$DATA_AT_PATH
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be +DATA_AT_PATH? maybe add a separator between the two.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the confusing part of using "$" for bulk strings. I am using it in all of the documentation to represent a variable (ala bash). So this should stay variable "DATA_AT_PATH" because we cannot say what type it will be.

@proppy
Copy link
Contributor

proppy commented Feb 12, 2016

We should also take a look at alternative like https://github.com/jeelabs/esp-link#outbound-http-rest-requests-and-mqtt-client (with https://github.com/jeelabs/el-client client) or https://github.com/tuanpmt/espduino and see how the protocol/usage compare to it.

Their protocol is apparently based on SLIP.

@ed7coyne
Copy link
Collaborator Author

We should take a look at them. In general I would be against using the SLIP protocol though. I think human readable is really the right approach for us, ease of comprehension and debugging should be pretty high on our priority list.

But that is my personal bias, this was the reason I selected redis for my personal projects.


The event stream will continue until you send CANCEL_STREAM.

When there is an ongoing event stream no other commands can be processed until you call END_STREAM as the event stream owns the return line.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this note in here as well. I don't see a clean way around this, let me know if you disagree.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need different command for CANCEL_STREAM and END_STREAM?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: "no other commands can be processed", since our protocol is serial based the client will have to poll if Serial.available() Serial.readline() for stream result, maybe it wouldn't hurt to make that more explicit with a command? that way this could be multiplexed with other operation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed CANCEL_STREAM to END_STREAM, I think they should be the same.

I am not certain what you mean by the second comment. What would you like to make explicit? Do you want to cache the stream on the modem and have the client call "NEXT_EVENT"? We may be able to do that but memory could be an issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't memory already an issue if we print something to Serial and nobody is reading on the other side?

We could have it read from the wifi connection only when we receive the NEXT_EVENT command?

I'm not sure which one is better, I was just thinking out loud.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the serial buffer fills up I think the serial write will just block until it is read. Also if the client can't do anything else with the connection they are less likely to leave it blocked than if they can just start a stream then ignore it while they do other things.

If we leave it in the wifi queue we are back to the state of not being able to do other actions while streaming. All other commands would have to return an error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said we can address the issue directly and measure memory usage, if it exceeds a certain amount shutdown the stream and return an error when they call "NEXT_EVENT".

However if we can stream directly to the serial line than I think we can handle much larger/faster streams more efficiently. Maybe are back to adding more modes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we leave it in the wifi queue we are back to the state of not being able to do other actions while streaming. All other commands would have to return an error.

So practically, it would have to be a different "connection", since the stream endpoint is different from the API endpoint (remember the 2x redirect you can after calling /stream)


Every time a serial connection is established we will expect a “BEGIN” call after which any subsequent calls can be made, until the connection is closed.

In the following examples we use $ to represent variables. For example $Host represents your firebase host.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: since $ is already used in the protocol, maybe we should using a different delimeter for placeholder like %VALUE%, [VALUE] or <VALUE>

@proppy
Copy link
Contributor

proppy commented Apr 8, 2016

LGTM just some nits.

@ed7coyne ed7coyne merged commit d674692 into FirebaseExtended:master Apr 8, 2016
@ed7coyne ed7coyne deleted the Serial branch April 8, 2016 20:22
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants