Basic Fujinet network support, iis-tracker works
parent
9f36507fa5
commit
33ea1f6e7a
@ -0,0 +1,116 @@
|
||||
package fujinet
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
See:
|
||||
https://github.com/FujiNetWIFI/fujinet-platformio/tree/master/lib/fnjson
|
||||
https://github.com/FujiNetWIFI/fujinet-platformio/wiki/JSON-Query-Format
|
||||
*/
|
||||
|
||||
type FnJson struct {
|
||||
data any
|
||||
Result []uint8
|
||||
}
|
||||
|
||||
func NewFnJson() *FnJson {
|
||||
var js FnJson
|
||||
return &js
|
||||
}
|
||||
|
||||
func (js *FnJson) Parse(data []uint8) ErrorCode {
|
||||
// See FNJSON::parse()
|
||||
err := json.Unmarshal(data, &js.data)
|
||||
if err != nil {
|
||||
return NetworkErrorJsonParseError
|
||||
}
|
||||
return NoError
|
||||
}
|
||||
|
||||
func (js *FnJson) Query(query []uint8) {
|
||||
// See FNJSON::setReadQuery
|
||||
// See https://github.com/kbranigan/cJSON/blob/master/cJSON_Utils.c
|
||||
if query[len(query)-1] == 0 {
|
||||
query = query[0 : len(query)-1]
|
||||
}
|
||||
queryString := string(query)
|
||||
queryString = strings.TrimSuffix(queryString, "/0")
|
||||
queryString = strings.TrimPrefix(queryString, "/")
|
||||
queryString = strings.TrimSuffix(queryString, "/")
|
||||
path := strings.Split(queryString, "/")
|
||||
|
||||
js.Result = nil
|
||||
current := js.data
|
||||
for i := 0; i < len(path); i++ {
|
||||
switch v := current.(type) {
|
||||
case map[string]any:
|
||||
var found bool
|
||||
current, found = v[path[i]]
|
||||
if !found {
|
||||
// Not found
|
||||
return
|
||||
}
|
||||
case []any:
|
||||
index, err := strconv.Atoi(path[i])
|
||||
if err != nil {
|
||||
// Path for arrays should be an int
|
||||
return
|
||||
}
|
||||
if index < 0 || index >= len(v) {
|
||||
// Index out of bounds
|
||||
return
|
||||
}
|
||||
current = v[index]
|
||||
default:
|
||||
// It's a leaf. We can't go down
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
js.Result = getJsonValue(current)
|
||||
}
|
||||
|
||||
func getJsonValue(data any) []uint8 {
|
||||
// See FNJson::getValue
|
||||
if data == nil {
|
||||
return []uint8("NULL")
|
||||
}
|
||||
|
||||
switch v := data.(type) {
|
||||
case bool:
|
||||
if v {
|
||||
return []uint8("TRUE")
|
||||
} else {
|
||||
return []uint8("FALSE")
|
||||
}
|
||||
case float64:
|
||||
//if math.Floor(v) == v { // As done in FNJSON__getValue()
|
||||
// It's an integer
|
||||
return []uint8(strconv.Itoa(int(v)))
|
||||
//} else {
|
||||
// return []uint8(fmt.Sprintf("%.10f", v))
|
||||
//}
|
||||
case string:
|
||||
return []uint8(v)
|
||||
case []any:
|
||||
s := make([]uint8, 0)
|
||||
for i := 0; i < len(v); i++ {
|
||||
s = append(s, getJsonValue(v[i])...)
|
||||
}
|
||||
return s
|
||||
case map[string]any:
|
||||
s := make([]uint8, 0)
|
||||
for k, e := range v {
|
||||
s = append(s, []uint8(k)...)
|
||||
s = append(s, getJsonValue(e)...)
|
||||
}
|
||||
return s
|
||||
default:
|
||||
// Should not be possible for an object unmarshalled from a JSON
|
||||
return []uint8("UNKNOWN")
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
package fujinet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testQuerys(t *testing.T, message string, queries [][]string) {
|
||||
js := NewFnJson()
|
||||
errorCode := js.Parse([]uint8(message))
|
||||
|
||||
if errorCode != NoError {
|
||||
t.Fatalf("Parse error %v. It should be %v", errorCode, NoError)
|
||||
}
|
||||
|
||||
for _, pair := range queries {
|
||||
js.Query([]uint8(pair[0]))
|
||||
result := string(js.Result)
|
||||
if result != pair[1] {
|
||||
t.Errorf("Query for %s, returned %s. It should be %s", pair[0], result, pair[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryMap(t *testing.T) {
|
||||
// See https://github.com/FujiNetWIFI/fujinet-apps/blob/master/iss-tracker/apple2/src/satellite.c
|
||||
testMessage := `{"timestamp": 1667218311, "message": "success", "iss_position": {"latitude": "21.3276", "longitude": "-39.4989"}}`
|
||||
|
||||
testCases := [][]string{
|
||||
{"/iss_position/longitude", "-39.4989"},
|
||||
{"/iss_position/latitude", "21.3276"},
|
||||
{"/timestamp", "1667218311"},
|
||||
}
|
||||
testQuerys(t, testMessage, testCases)
|
||||
}
|
||||
|
||||
// See https://github.com/FujiNetWIFI/fujinet-apps/blob/master/json-test/atari/jsontest.c
|
||||
const testArrayMessage = `
|
||||
[
|
||||
{
|
||||
"id": "108361296757279278",
|
||||
"created_at": "2022-05-25T07:00:06.000Z",
|
||||
"in_reply_to_id": null,
|
||||
"in_reply_to_account_id": null,
|
||||
"sensitive": false,
|
||||
"spoiler_text": "",
|
||||
"visibility": "public",
|
||||
"language": "en",
|
||||
"uri": "https://botsin.space/users/osxthemes/statuses/108361286061805267",
|
||||
"url": "https://botsin.space/@osxthemes/108361286061805267",
|
||||
"replies_count": 0,
|
||||
"reblogs_count": 0,
|
||||
"favourites_count": 0,
|
||||
"edited_at": null,
|
||||
"local_only": null,
|
||||
"content": "<p>Floppies! - Robert Davis</p>",
|
||||
"reblog": null,
|
||||
"account": {
|
||||
"id": "23439",
|
||||
"username": "osxthemes",
|
||||
"acct": "osxthemes@botsin.space",
|
||||
"display_name": "Macintosh Themes",
|
||||
"locked": false,
|
||||
"bot": true,
|
||||
"discoverable": true,
|
||||
"group": false,
|
||||
"created_at": "2018-03-28T00:00:00.000Z",
|
||||
"note": "<p>I tweet Mac OSX (pre-10.5) and Kaleidoscope (Classic) themes. Bot by <span class=\"h-card\"><a href=\"https://octodon.social/@Eramdam\" class=\"u-url mention\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">@<span>Eramdam</span></a></span>, inspired by kaleidoscopemac@twitter.com. Also on Twitter at <a href=\"https://twitter.com/osxthemes\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">twitter.com/osxthemes</span><span class=\"invisible\"></span></a></p>",
|
||||
"url": "https://botsin.space/@osxthemes",
|
||||
"avatar": "https://assets.oldbytes.space/assets.oldbytes.space/accounts/avatars/000/023/439/original/322ac0c621b55624.png",
|
||||
"avatar_static": "https://assets.oldbytes.space/assets.oldbytes.space/accounts/avatars/000/023/439/original/322ac0c621b55624.png",
|
||||
"header": "https://assets.oldbytes.space/assets.oldbytes.space/cache/accounts/headers/000/023/439/original/ea0e0cd513b5a9f7.png",
|
||||
"header_static": "https://assets.oldbytes.space/assets.oldbytes.space/cache/accounts/headers/000/023/439/original/ea0e0cd513b5a9f7.png",
|
||||
"followers_count": 157,
|
||||
"following_count": 1,
|
||||
"statuses_count": 17615,
|
||||
"last_status_at": "2022-05-25",
|
||||
"emojis": [],
|
||||
"fields": []
|
||||
},
|
||||
"media_attachments": [
|
||||
{
|
||||
"id": "108361296738754794",
|
||||
"type": "image",
|
||||
"url": "https://assets.oldbytes.space/assets.oldbytes.space/cache/media_attachments/files/108/361/296/738/754/794/original/5785ab0a51d0db1f.gif",
|
||||
"preview_url": "https://assets.oldbytes.space/assets.oldbytes.space/cache/media_attachments/files/108/361/296/738/754/794/small/5785ab0a51d0db1f.png",
|
||||
"remote_url": "https://files.botsin.space/media_attachments/files/108/361/285/793/211/606/original/7fe52f343cf0c99a.gif",
|
||||
"preview_remote_url": null,
|
||||
"text_url": null,
|
||||
"meta": {
|
||||
"original": {
|
||||
"width": 213,
|
||||
"height": 181,
|
||||
"size": "213x181",
|
||||
"aspect": 1.1767955801104972
|
||||
},
|
||||
"small": {
|
||||
"width": 213,
|
||||
"height": 181,
|
||||
"size": "213x181",
|
||||
"aspect": 1.1767955801104972
|
||||
}
|
||||
},
|
||||
"description": "Floppies! - Robert Davis",
|
||||
"blurhash": "UbLNcMO@QkAAx{jJX4V@8yX9xYX7D@kXoZkV"
|
||||
}
|
||||
],
|
||||
"mentions": [],
|
||||
"tags": [],
|
||||
"emojis": [],
|
||||
"card": null,
|
||||
"poll": null
|
||||
}
|
||||
]`
|
||||
|
||||
func TestQueryArray(t *testing.T) {
|
||||
|
||||
testCases := [][]string{
|
||||
{"/0/account/display_name", "Macintosh Themes"},
|
||||
{"/0/created_at", "2022-05-25T07:00:06.000Z"},
|
||||
{"/0/content", "<p>Floppies! - Robert Davis</p>"},
|
||||
{"/0/nonexistent", "NULL"},
|
||||
{"/1/account/display_name", "NULL"},
|
||||
{"/-1/account/display_name", "NULL"},
|
||||
{"/zz/account/display_name", "NULL"},
|
||||
{"/0/media_attachments/0/meta/original", "width213height181size213x181aspect1.1767955801"},
|
||||
}
|
||||
testQuerys(t, testArrayMessage, testCases)
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package fujinet
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Protocol interface {
|
||||
Open(urlParsed *url.URL)
|
||||
Close()
|
||||
ReadAll() ([]uint8, ErrorCode)
|
||||
Write(data []uint8) error
|
||||
}
|
||||
|
||||
type ErrorCode uint8
|
||||
|
||||
const (
|
||||
// See fujinet-platformio/lib/network-protocol/status_error_codes.h
|
||||
NoError = ErrorCode(0)
|
||||
NetworkErrorEndOfFile = ErrorCode(136)
|
||||
NetworkErrorGeneral = ErrorCode(144)
|
||||
NetworkErrorNotImplemented = ErrorCode(146)
|
||||
NetworkErrorInvalidDeviceSpec = ErrorCode(165)
|
||||
|
||||
// New
|
||||
NetworkErrorJsonParseError = ErrorCode(250)
|
||||
)
|
||||
|
||||
func InstantiateProtocol(urlParsed *url.URL, method uint8) (Protocol, ErrorCode) {
|
||||
scheme := strings.ToUpper(urlParsed.Scheme)
|
||||
switch scheme {
|
||||
case "HTTP":
|
||||
return newProtocolHttp(method), NoError
|
||||
case "HTTPS":
|
||||
return newProtocolHttp(method), NoError
|
||||
default:
|
||||
return nil, NetworkErrorGeneral
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package fujinet
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type protocolHttp struct {
|
||||
method uint8
|
||||
url *url.URL
|
||||
}
|
||||
|
||||
func newProtocolHttp(method uint8) *protocolHttp {
|
||||
var p protocolHttp
|
||||
p.method = method
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *protocolHttp) Open(urlParsed *url.URL) {
|
||||
p.url = urlParsed
|
||||
}
|
||||
|
||||
func (p *protocolHttp) Close() {
|
||||
|
||||
}
|
||||
|
||||
func (p *protocolHttp) ReadAll() ([]uint8, ErrorCode) {
|
||||
if p.method == 12 /*GET*/ {
|
||||
resp, err := http.Get(p.url.String())
|
||||
if err != nil {
|
||||
return nil, NetworkErrorGeneral
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, NetworkErrorGeneral
|
||||
}
|
||||
return data, NoError
|
||||
}
|
||||
|
||||
return nil, NetworkErrorNotImplemented
|
||||
}
|
||||
|
||||
func (p *protocolHttp) Write(data []uint8) error {
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue