added microjson lib,

implemented json parsing,
fixed litterally 200 errors since the IDE did not display them until now (I was already suspicious because everything went so well),
I even fixed the library,
some functionality is now commented since there are still errors. I marked them with a //TODO (mainly splitflap_wrapper.c)
to conclude: it now builds again
main
Jonas Arnold 4 years ago
parent 6b7d7e0635
commit 1aa3a601d7
  1. 1
      ADIS_ESP32_Eclipse/CMakeLists.txt
  2. 7
      ADIS_ESP32_Eclipse/lib/CMakeLists.txt
  3. 27
      ADIS_ESP32_Eclipse/lib/microjson-1.6/COPYING
  4. 66
      ADIS_ESP32_Eclipse/lib/microjson-1.6/Makefile
  5. 24
      ADIS_ESP32_Eclipse/lib/microjson-1.6/NEWS.adoc
  6. 14
      ADIS_ESP32_Eclipse/lib/microjson-1.6/README.adoc
  7. 22
      ADIS_ESP32_Eclipse/lib/microjson-1.6/control
  8. 34
      ADIS_ESP32_Eclipse/lib/microjson-1.6/example1.c
  9. 50
      ADIS_ESP32_Eclipse/lib/microjson-1.6/example2.c
  10. 67
      ADIS_ESP32_Eclipse/lib/microjson-1.6/example3.c
  11. 43
      ADIS_ESP32_Eclipse/lib/microjson-1.6/example4.c
  12. 497
      ADIS_ESP32_Eclipse/lib/microjson-1.6/microjson.adoc
  13. 75
      ADIS_ESP32_Eclipse/lib/microjson-1.6/mjson.adoc
  14. 879
      ADIS_ESP32_Eclipse/lib/microjson-1.6/mjson.c
  15. 164
      ADIS_ESP32_Eclipse/lib/microjson-1.6/mjson.h
  16. 978
      ADIS_ESP32_Eclipse/lib/microjson-1.6/test_microjson.c
  17. 117
      ADIS_ESP32_Eclipse/lib/microjson-1.6/test_microjson_wignore.c
  18. 1
      ADIS_ESP32_Eclipse/main/CMakeLists.txt
  19. 2
      ADIS_ESP32_Eclipse/main/Shell.c
  20. 28
      ADIS_ESP32_Eclipse/main/challenge_app.c
  21. 6
      ADIS_ESP32_Eclipse/main/challenge_app.h
  22. 66
      ADIS_ESP32_Eclipse/main/challenge_com.c
  23. 10
      ADIS_ESP32_Eclipse/main/challenge_com.h
  24. 2
      ADIS_ESP32_Eclipse/main/platform.c
  25. 30
      ADIS_ESP32_Eclipse/main/splitflap_wrapper.c
  26. 3
      ADIS_ESP32_Eclipse/main/splitflap_wrapper.h

@ -4,6 +4,7 @@ cmake_minimum_required(VERSION 3.5)
# add my own component direcotry to the list # add my own component direcotry to the list
list(APPEND EXTRA_COMPONENT_DIRS "../McuLib") list(APPEND EXTRA_COMPONENT_DIRS "../McuLib")
list(APPEND EXTRA_COMPONENT_DIRS "lib")
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# additional include for IncludeMcuLibConfig # additional include for IncludeMcuLibConfig

@ -0,0 +1,7 @@
idf_component_register(
SRCS
"microjson-1.6/mjson.c"
INCLUDE_DIRS
"./microjson-1.6"
)

@ -0,0 +1,27 @@
BSD LICENSE
Copyright (c) 2015, Eric S. Raymond
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,66 @@
# Makefile for the microjson project
# The version for release is derived from the mostt recent stanza in the news file.
VERSION=$(shell sed -n <NEWS.adoc '/::/s/^\([0-9][^:]*\).*/\1/p' | head -1)
CFLAGS = -O
# Add DEBUG_ENABLE for the tracing code
CFLAGS += -DDEBUG_ENABLE -g
# Add TIME_ENABLE to support RFC3339 time literals
CFLAGS += -DTIME_ENABLE
all: mjson.o test_microjson example1 example2 example3 example4
mjson.o: mjson.c mjson.h
test_microjson: test_microjson.o mjson.o
$(CC) $(CFLAGS) -o test_microjson test_microjson.o mjson.o
test_microjson_wignore: test_microjson_wignore.o mjson.o
$(CC) $(CFLAGS) -o test_microjson_wignore test_microjson_wignore.o mjson.o
.SUFFIXES: .html .adoc .3
# Requires asciidoc and xsltproc/docbook stylesheets.
.adoc.html:
asciidoc $*.adoc
.adoc.3:
a2x --doctype manpage --format manpage $*.adoc
# Regression test
check: test_microjson test_microjson_wignore
./test_microjson
./test_microjson_wignore
# Worked examples. These are essentially subsets of the regression test.
example1: example1.c mjson.c mjson.h
example2: example2.c mjson.c mjson.h
example3: example3.c mjson.c mjson.h
example4: example4.c mjson.c mjson.h
clean:
rm -f mjson.o test_microjson.o test_microjson test_microjson_wignore example[1234]
rm -f microjson.html mjson.html
version:
@echo $(VERSION)
SUPPRESSIONS = --suppress=unusedStructMember --suppress=unreadVariable
SUPPRESSIONS += -U__UNUSED__
cppcheck:
cppcheck -I. --template gcc --enable=all $(SUPPRESSIONS) *.[ch]
SOURCES = Makefile *.[ch]
DOCS = README.adoc COPYING NEWS.adoc control microjson.adoc mjson.adoc
ALL = $(SOURCES) $(DOCS)
microjson-$(VERSION).tar.gz: $(ALL)
tar --transform='s:^:microjson-$(VERSION)/:' --show-transformed-names -cvzf microjson-$(VERSION).tar.gz $(ALL)
dist: microjson-$(VERSION).tar.gz
release: microjson-$(VERSION).tar.gz microjson.html mjson.html
shipper version=$(VERSION) | sh -e -x
refresh: microjson.html mjson.html
shipper -N -w version=$(VERSION) | sh -e -x

@ -0,0 +1,24 @@
= microjson project news =
1.6: 2020-07-12::
It's now possible to match all previously unspecified fields ignored.
Whitespace after values is now skpped properly in more circumstances.
Integer values 0 and 1 are accepted for bools.
1.5: 2019-03-06::
It's now possible to read objects inside of objects (not just in arrays).
1.4: 2018-08-09::
Add a belt-and suspenders bounds check side-ported from the GPSD code.
1.3: 2014-09-28::
Add a time_t type that is an RFC3339 date, stored as Unix seconds.
1.2: 2014-09-27::
Added a manual page.
1.1: 2014-09-26::
Documentation improvements basde on initial feedback.
1.0: 2014-09-25::
Initial release.

@ -0,0 +1,14 @@
= README for microjson =
This is a tiny JSON parser in C that uses only fixed-extent storage.
This code parses the largest subset of JSON that can be unpacked into
static C structures, without using malloc(3). It is very small and
very low-footprint, intended for use in constrained embedded
environments. It may also be of interest for code where malloc is
verboten to reduce reliability and security vulnerabilities.
Apologies about the name collision; I didn't think to check for other
projects with this name when I first created it.
There's a manual page, link:mjson.html[], and a tutorial: link:microjson.html[]

@ -0,0 +1,22 @@
# This is not a real Debian control file
# It's project metadata for the shipper tool
Package: microjson
Description: Tiny JSON parser in C that uses only fixed-extent storage.
This code parses the largest subset of JSON that can be unpacked into static C
structures, without using malloc(3). It is very small and very low-footprint,
intended for use in constrained embedded environments. It may also be of
interest for code where malloc is verboten to reduce reliability and security
vulnerabilities.
Homepage: http://www.catb.org/~esr/microjson
XBS-HTML-Target: index.html
XBS-Repository-URL: https://gitlab.com/esr/microjson
#XBS-Project-Tags: JSON, C
XBS-VC-Tag-Template: %(version)s

@ -0,0 +1,34 @@
/* example1.c - first example in the programming guide
*
* This file is Copyright (c) 2014 by Eric S. Raymond
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "mjson.h"
static bool flag1, flag2;
static int count;
static const struct json_attr_t json_attrs[] = {
{"count", t_integer, .addr.integer = &count},
{"flag1", t_boolean, .addr.boolean = &flag1,},
{"flag2", t_boolean, .addr.boolean = &flag2,},
{NULL},
};
int main(int argc, char *argv[])
{
int status = 0;
status = json_read_object(argv[1], json_attrs, NULL);
printf("status = %d, count = %d, flag1 = %d, flag2 = %d\n",
status, count, flag1, flag2);
if (status != 0)
puts(json_error_string(status));
}
/* end */

@ -0,0 +1,50 @@
/* example2.c - second example in the programming guide
*
* This file is Copyright (c) 2014 by Eric S. Raymond
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <stdio.h>
#include <stdlib.h>
#include "mjson.h"
#define MAXCHANNELS 72
static bool usedflags[MAXCHANNELS];
static int PRN[MAXCHANNELS];
static int elevation[MAXCHANNELS];
static int azimuth[MAXCHANNELS];
static int visible;
const struct json_attr_t sat_attrs[] = {
{"PRN", t_integer, .addr.integer = PRN},
{"el", t_integer, .addr.integer = elevation},
{"az", t_integer, .addr.integer = azimuth},
{"used", t_boolean, .addr.boolean = usedflags},
{NULL},
};
const struct json_attr_t json_attrs_sky[] = {
{"class", t_check, .dflt.check = "SKY"},
{"satellites", t_array, .addr.array.element_type = t_object,
.addr.array.arr.objects.subtype=sat_attrs,
.addr.array.maxlen = MAXCHANNELS,
.addr.array.count = &visible},
{NULL},
};
int main(int argc, char *argv[])
{
int i, status = 0;
status = json_read_object(argv[1], json_attrs_sky, NULL);
printf("%d satellites:\n", visible);
for (i = 0; i < visible; i++)
printf("PRN = %d, elevation = %d, azimuth = %d\n",
PRN[i], elevation[i], azimuth[i]);
if (status != 0)
puts(json_error_string(status));
}
/* end */

@ -0,0 +1,67 @@
/* example2.c - first example in the programming guide
*
* This file is Copyright (c) 2014 by Eric S. Raymond
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <limits.h>
#include <string.h>
#include "mjson.h"
#define MAXUSERDEVS 4
struct devconfig_t {
char path[PATH_MAX];
double activated;
};
struct devlist_t {
int ndevices;
struct devconfig_t list[MAXUSERDEVS];
};
static struct devlist_t devicelist;
static int json_devicelist_read(const char *buf)
{
const struct json_attr_t json_attrs_subdevice[] = {
{"path", t_string, STRUCTOBJECT(struct devconfig_t, path),
.len = sizeof(devicelist.list[0].path)},
{"activated", t_real, STRUCTOBJECT(struct devconfig_t, activated)},
{NULL},
};
const struct json_attr_t json_attrs_devices[] = {
{"class", t_check,.dflt.check = "DEVICES"},
{"devices", t_array, STRUCTARRAY(devicelist.list,
json_attrs_subdevice,
&devicelist.ndevices)},
{NULL},
};
int status;
memset(&devicelist, '\0', sizeof(devicelist));
status = json_read_object(buf, json_attrs_devices, NULL);
if (status != 0) {
return status;
}
return 0;
}
int main(int argc, char *argv[])
{
int i, status = 0;
status = json_devicelist_read(argv[1]);
printf("%d devices:\n", devicelist.ndevices);
for (i = 0; i < devicelist.ndevices; i++)
printf("%s @ %f\n",
devicelist.list[i].path, devicelist.list[i].activated);
if (status != 0)
puts(json_error_string(status));
}
/* end */

@ -0,0 +1,43 @@
/* example4.c - fourth example in the programming guide
*
* This file is Copyright (c) 2020 by Eric S. Raymond
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mjson.h"
#define ARR1_LENGTH 8
static bool flag1;
static int arr1[ARR1_LENGTH];
static int arr1_count;
const struct json_attr_t json_attrs_example4[] = {
{"flag1", t_boolean, .addr.boolean = &flag1},
{"arr1", t_array, .addr.array.element_type = t_integer,
.addr.array.arr.integers = arr1,
.addr.array.maxlen = ARR1_LENGTH,
.addr.array.count = &arr1_count},
{NULL},
};
int main(int argc, char *argv[])
{
const char* end = (const char*) argv[1] + strlen((const char*) argv[1]);
const char* cur = (const char*) argv[1];
while (cur < end) {
int status = json_read_object(cur, json_attrs_example4, &cur);
printf("status: %d, flag1: %d\n", status, flag1);
for (int i = 0; i < arr1_count; i++)
printf("arr1 = %d\n", arr1[i]);
if (status != 0)
puts(json_error_string(status));
arr1_count = 0;
}
}
/* end */

@ -0,0 +1,497 @@
= Building static JSON parsers with microjson =
Eric S. Raymond <esr@thyrsus.com>
== Overview ==
microjson is a tiny parser for the largest subset of JSON (JavaScript Object
Notation) that can be unpacked to C static storage. It uses entirely
fixed-extent memory, no malloc(). It is thus very suitable for use in
memory-constrained environments such as embedded systems; also for
long-running service daemons that must provably not leak memory.
microjson is extremely well-tested code. This is essentially the same
parser used in GPSD and its client libraries, which have hundreds of
millions of deployments underneath Google Maps in Android phones.
microjson parses JSON from string input and unpacks the content
directly into static storage declared by the calling program.
You give it a set of template structures describing the expected shape
of the incoming JSON, and it will error out if that shape is not
matched. When the parse succeeds, attribute values will be extracted
into static locations specified in the template structures.
== How To Use This Document ==
This is a fast tutorial for working programmers. It teaches by
examples; if you read the code carefully it will tell you
more than the accompanying text. Just read it in sequence, trying not
to skip anything.
All the examples are shipped in the microjson distribution. Most are
not synthetic toys, but stripped-down versions of working code from
GPSD. Copy them freely. You may also want to look at the source for
test_microjson.c, the regression test; it exercises most cases.
== An Example ==
Here is nearly the simplest possible example:
.Example 1
---------------------------------------------------------------------
/*
* Parse JSON shaped like '{"flag1":true,"flag2":false,"count":42}'
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "mjson.h"
static bool flag1, flag2;
static int count;
static const struct json_attr_t json_attrs[] = {
{"count", t_integer, .addr.integer = &count},
{"flag1", t_boolean, .addr.boolean = &flag1,},
{"flag2", t_boolean, .addr.boolean = &flag2,},
{NULL},
};
int main(int argc, char *argv[])
{
int status = 0;
status = json_read_object(argv[1], json_attrs, NULL);
printf("status = %d, count = %d, flag1 = %d, flag2 = %d\n",
status, count, flag1, flag2);
if (status != 0)
puts(json_error_string(status));
}
---------------------------------------------------------------------
And here are some invocations:
---------------------------------------------------------------------
$ example1 '{"flag1":true,"flag2":false,"count":42}'
status = 0, count = 42, flag1 = 1, flag2 = 0
$ example1 '{"flag1":true,"flag2":false,"count":23}'
status = 0, count = 23, flag1 = 1, flag2 = 0
$ example1 '{"whozis":true,"flag2":false,"count":23}'
status = 3, count = 0, flag1 = 0, flag2 = 0
unknown attribute name
$ example1 '{"flag1":true,"flag2":false,"count":23,"whozis":"whatsis"}'
status = 3, count = 23, flag1 = 1, flag2 = 0
unknown attribute name
---------------------------------------------------------------------
The +json_read_object()+ call unpacks the values in the argument JSON
object into three static variables. In many uses the target locations
would instead be storage in some static structure instance.
In this example, the +json_attrs+ structure array associates each
possible member name with a type and a target address. The function
+json_read_object()+ treats this array of constants as parsing
instructions.
When an unexpected attribute name is encountered, the parser normally
terminates, returning an error status (but it is possible to mage the
parser ignore unknown attributes instead). Attributes and values
parsed before a terminating error modify their target storage.
The parser recognizes a wider range of types than this, and the
template structures can specify defaults when an expected JSON
attribute is omitted. Most of the rest is details.
== Theory of Operation ==
The parser is a simple state machine that walks the input looking
for syntactically well-formed attribute-value pairs. Each time it
finds one, it looks up the name in the template structure array
driving the parse. The type tells it how to interpret the
value; the target address tells it where to put the value.
Syntax errors, or any unknown attribute name, terminate the parse.
That is unless the wildcard ignore option is used.
One consequence to be aware of is that if an input JSON object
contains multiple attribute-value pairs with the same attribute,
the associated storage will be modified each time and only
the last setting will be effective.
=== Simple Value Types ===
The type field of a +json_attr_t+ structure can have the following
'simple' alternatives, each corresponding to an atomic JSON value:
+t_check+: Value of this attribute must match a specified string,
or the parse will fail with a distinguishable error.
+t_integer+: Parse a single signed integer literal, copy the value
to a C +int+ location. Uses +strtol()+.
+t_uinteger+: Parse a single signed integer literal, copy the value
to a C +unsigned int+ location. Uses +strtoul()+.
+t_real+: Parse a single signed float literal, copy the value
to a C +double+ location. Uses +strtod()+.
+t_boolean+: Accept one of the JSON literals +true+ or +false+,
copy the value to a C +bool+ location.
+t_string+: Accept a JSON string literal, copy the contents to a
C char buffer.
+t_character+: Accept a single-character JSON string literal, copy
that character to a C +char+ location.
+t_time+" Accept a string that is an RFC3339 timestamp (full ISO-8601
date/time in Zulu time with optional fractional decimal seconds).
Store as a double value, seconds since Unix epoch. Accepted only
if the code was built with -DTIME_ENABLE; introduces a dependency
on the glibc function timegm().
Associated with each simple value type's storage (in the +addr+
union) is a correspondingly-named field in the +dflt+ union).
This is a default value which is copied to the target storage
when the JSON object does not contain the corresponding attribute.
You can turn off this defaulting behavior by setting the +nodefault+
member to +true+.
=== Enumerated-value types ===
The parser includes support for string attributes with controlled
vocabularies.
A +json_attr_t+ instance with a +t_integer+ or +t_uinteger+ type field
can point at a map (an array of +json_enum_t+ structures) that lists
names and pairs of integral values. If this is done, the parser
expects the values of the JSON attribute to be strings but internally
maps them to corresponding integer values before setting the target
storage. An un-enumerated string value causes the parse to error out.
(Case 8 in the unit test source code illustrates how to use this feature.)
=== Compound Value Types ===
The following cases do not parse JSON value atoms:
==== Skip fields ====
t_ignore: Value of this attribute is ignored. Significant because
unexpected attribute names cause the parse to terminate with error.
An empty attribute name may be used to wildcard ignore all unknown
fields. This should rarely be used and always as penultimate to the
terminating NULL.
==== Sub-objects ====
t_object: It is possible to parse JSON objects within JSON objects.
See case 14 in the unit test for an example.
==== Parallel arrays ===
t_array: Value of this attribute is expected to be a homogenous array.
Another field of the structure specifies the array's element type,
which can be any simple type or t_object (meaning a JSON subobject).
If the array has simple elements, three additional things must be
specified: the base address of the array's storage, the maximum number
of elements it can have, and an integer address where the parser will
place a count of elements filled in.
Simple array values always default to zero for numeric types, +false+
for booleans, and NULL for strings.
The array element type may be +t_object+, as in the +satellites+ field
in this example:
.Example 2
------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include "mjson.h"
#define MAXCHANNELS 72
static bool usedflags[MAXCHANNELS];
static int PRN[MAXCHANNELS];
static int elevation[MAXCHANNELS];
static int azimuth[MAXCHANNELS];
static int visible;
const struct json_attr_t sat_attrs[] = {
{"PRN", t_integer, .addr.integer = PRN},
{"el", t_integer, .addr.integer = elevation},
{"az", t_integer, .addr.integer = azimuth},
{"used", t_boolean, .addr.boolean = usedflags},
{NULL},
};
const struct json_attr_t json_attrs_sky[] = {
{"class", t_check, .dflt.check = "SKY"},
{"satellites", t_array, .addr.array.element_type = t_object,
.addr.array.arr.objects.subtype=sat_attrs,
.addr.array.maxlen = MAXCHANNELS,
.addr.array.count = &visible},
{NULL},
};
int main(int argc, char *argv[])
{
int i, status = 0;
status = json_read_object(argv[1], json_attrs_sky, NULL);
printf("%d satellites:\n", visible);
for (i = 0; i < visible; i++)
printf("PRN = %d, elevation = %d, azimuth = %d\n",
PRN[i], elevation[i], azimuth[i]);
if (status != 0)
puts(json_error_string(status));
}
------------------------------------------------------------------------
Here's an example invocation (string literal folded for readability):
--------------------------------------------------------
$ example2 '{"class":"SKY","satellites":
[{"PRN":10,"el":45,"az":196,"used":true},
{"PRN":29,"el":67,"az":310,"used":true}]}'
2 satellites:
PRN = 10, elevation = 45, azimuth = 196
PRN = 29, elevation = 67, azimuth = 310
--------------------------------------------------------
In this case, the parser needs to be told where to find a template
array describing how to parse the element objects. The target addresses
in this structure will point to the base addressees of parallel arrays.
The arrays are filled in until the parser runs out of conforming JSON
sub-objects to parse or would exceed the +maxlen+ count of elements.
More formally: parallel object arrays take one base address per object
subfield, and are mapped into parallel C arrays (one per subfield).
Strings are not supported in this kind of array, as they don't have a
"natural" fixed size to use as an offset multiplier.
The default of array elements is always zero (false for booleans, NULL
for strings).
==== Structure arrays ====
There's a different way to parse arrays that can unpack an
array of JSON objects directly into an array of C structs.
.Example 3:
--------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <stdbool.h>
#include <stddef.h>
#include <limits.h>
#include <string.h>
#include "mjson.h"
#define MAXUSERDEVS 4
struct devconfig_t {
char path[PATH_MAX];
double activated;
};
struct devlist_t {
int ndevices;
struct devconfig_t list[MAXUSERDEVS];
};
static struct devlist_t devicelist;
static int json_devicelist_read(const char *buf)
{
const struct json_attr_t json_attrs_subdevice[] = {
{"path", t_string, STRUCTOBJECT(struct devconfig_t, path),
.len = sizeof(devicelist.list[0].path)},
{"activated", t_real, STRUCTOBJECT(struct devconfig_t, activated)},
{NULL},
};
const struct json_attr_t json_attrs_devices[] = {
{"class", t_check,.dflt.check = "DEVICES"},
{"devices", t_array, STRUCTARRAY(devicelist.list,
json_attrs_subdevice,
&devicelist.ndevices)},
{NULL},
};
int status;
memset(&devicelist, '\0', sizeof(devicelist));
status = json_read_object(buf, json_attrs_devices, NULL);
if (status != 0) {
return status;
}
return 0;
}
int main(int argc, char *argv[])
{
int i, status = 0;
status = json_devicelist_read(argv[1]);
printf("%d devices:\n", devicelist.ndevices);
for (i = 0; i < devicelist.ndevices; i++)
printf("%s @ %f\n",
devicelist.list[i].path, devicelist.list[i].activated);
if (status != 0)
puts(json_error_string(status));
}
--------------------------------------------------------
Here is an example:
--------------------------------------------------------
$ example3 '{"devices":[{"path":"/dev/ttyUSB0",
"activated":1411468340}]}'
1 devices:
/dev/ttyUSB0 @ 1411468340.000000
--------------------------------------------------------
In this case, the STRUCTARRAY and STRUCTOBJECT macros are clues to
what is going on. STRUCTOBJECT is a thin wrapper around offsetof();
STRUCTARRAY sets up the parser to walk through the array of
structures, filling each element as it goes.
More formally: structobject arrays are a way to parse a list of
objects to a set of modifications to a corresponding array of C
structs. The trick is that the array object initialization has to
specify both the C struct array's base address and the stride length
(the size of the C struct). If you initialize the offset fields with
the correct offsetof calls, everything will work. Strings are
supported but all string storage has to be inline in the struct.
== Parsing Concatenated Objects ==
The +end+ param of +json_read_object()+ can be re-used as the +cp+ param
to the same. As a result, a simple loop can be used to parse streamed or
concatenated root level JSON objects.
.Example 4
--------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mjson.h"
#define ARR1_LENGTH 8
static bool flag1;
static int arr1[ARR1_LENGTH];
static int arr1_count;
const struct json_attr_t json_attrs_example4[] = {
{"flag1", t_boolean, .addr.boolean = &flag1},
{"arr1", t_array, .addr.array.element_type = t_integer,
.addr.array.arr.integers = arr1,
.addr.array.maxlen = ARR1_LENGTH,
.addr.array.count = &arr1_count},
{NULL},
};
int main(int argc, char *argv[])
{
int i, status = 0;
const char* end = (const char*) argv[1] + strlen((const char*) argv[1]);
const char* cur = (const char*) argv[1];
while (cur < end) {
status = json_read_object(cur, json_attrs_example4, &cur);
printf("status: %d, flag1: %d\n", status, flag1);
for (i = 0; i < arr1_count; i++)
printf("arr1 = %d\n", arr1[i]);
if (status != 0)
puts(json_error_string(status));
arr1_count = 0;
}
}
--------------------------------------------------------
Here is an example:
--------------------------------------------------------
$ ./example4 '{"flag1":true} {"flag1":0,"arr1":[10,20]}
{"flag1":1} {"flag1":7, "arr1":[30,40,50]'
status: 0, flag1: 1
status: 0, flag1: 0
arr1 = 10
arr1 = 20
status: 0, flag1: 1
status: 0, flag1: 1
arr1 = 30
arr1 = 40
arr1 = 50
--------------------------------------------------------
(Test case 18 also illustrates how to use this feature.)
== Some Grubby Details ==
You have to specify the shape of the JSON you expect to parse in advance.
The "shape" of a JSON object is the type signature of its
attributes (and attribute values, and so on recursively down through
all nestings of objects and arrays). This parser is indifferent to
the order of attributes at any level, but you have to tell it in
advance what the type of each attribute value will be and where the
parsed value will be stored. The template structures may supply
default values to be used when an expected attribute is omitted.
The preceding paragraph told one fib. A single attribute may actually
have a span of multiple specifications with different syntactically
distinguishable types (e.g. string vs. real vs. integer vs. boolean,
but not signed integer vs. unsigned integer). The parser will match
the right spec against the actual data. (There's an instance
of this in Example 3.)
The dialect this parses has some limitations. First, it cannot
recognize the JSON "null" value. Second, all elements of an array must
be of the same type. Third, t_character may not be an array element
(this restriction could be lifted, and might be in a future release).
Third, both attribute names and string values have hard limits; these
can be tweaked by modifying the header file.
There are separate entry points for beginning a parse of either a JSON
object or a JSON array.
JSON "float" quantities are actually stored as doubles. Note that
float parsing uses +atof(3)+ and is thus locale-sensitive - this
affects whether period or comma is used as a decimal point. If in any
doubt, set the C numeric locale explicitly to match your data source.
You should not assume that the numeric values of error codes are
stable. Use the JSON_ERR_* names, not the numbers.
We have been informed that it is possible to core-dump this code by
passing NULL or bogus pointers to json_read_object(), so don't do
that. There's no sanity check against bad arguments in order to
keep the library small and light.
== Advanced Usage ==
This code is designed to be stripped down still further; do not be
afraid to copy mjson.c and drop out the parts you don't need (but
please leave in my name somewhere as original author).
It is a good idea, when possible, to generate your parse-template
structures programmatically from a higher-level description of the
JSON. GPSD uses this technique extensively.
// end

@ -0,0 +1,75 @@
= mjson(3) =
:doctype: manpage
== NAME ==
mjson - fast parse of JSON to fixed-extent C structures
== FUNCTIONS ==
----------------------------------------------------
#include "mjson.h"
int json_read_object(const char *, const struct json_attr_t *, const char **);
int json_read_array(const char *, const struct json_array_t *, const char **);
const char *json_error_string(int);
void json_enable_debug(int, FILE *);
----------------------------------------------------
== DESCRIPTION ==
+json_read_object()+ attempts to parse a whole JSON object from the
buffer pointed at by the first argument. The second argument points
at an array of template structures describing the expected shape of
the JSON object (that is, the set of attribute names and their
expected value types. order-independent) and specifying for each
attribute a C address where the parsed value should ve deivered.
The third argument, if non-null, is where a copy of a pointer
to just past the parsed object is placed.
+json_read_object()+ attempts to parse a whole JSON object from the
buffer pointed at by the first argument. The second argument points
at a template structure describing the array's element type and where
successive elements are to be placed. The third argument, if non-null,
is where a copy of a pointer to just past the parsed object is placed.
Objects may contain objects or arrays as attribute values, and an
array may be composed of JSON objects. These functions mutually
recurse as required. (Arrays within arrays are currently not
supported; this may change in a future release.)
+void json_enable_debug(int, FILE *)+ enables the generation of trace
messages to the indicated file pointer while parsing.
For details on how to build template structures, consult the document
_Building Static JSON Parsers With Microjson_ shipped with the
distribution.
== RETURN VALUE ==
The two main functions return 0 for success, a nonzero error code for
failure. The function +json_error_string()+ maps error codes to
explanatory messages.
When an error is returned and the end pointer (third) argument is
non-null, it is filled with the value of the buffer pointer at the
time the error was thrown.
== BUGS ==
A backslash escape of "\0000" in a string value will drop a NUL in the
string that is likely to confuse C code looking at the storage. In
general, the high byte of a \uxxxxx escape will be ignored and the
ASCII high-half byte named by the last two hex digits will be
inserted in the string.
There is no way to make a parser accept the typeless special JSON
value "null", because there is nothing in C's type ontology to map it
to.
JSON arrays are restricted to having a single homogenous element type.
However, the type may itself be a JSON object.
== REPORTING BUGS ==
Report bugs to Eric S. Raymond <esr@thyrsus.com>. The project page is
at http://catb.org/~esr/microjson

@ -0,0 +1,879 @@
/****************************************************************************
NAME
mjson.c - parse JSON into fixed-extent data structures
DESCRIPTION
This module parses a large subset of JSON (JavaScript Object
Notation). Unlike more general JSON parsers, it doesn't use malloc(3)
and doesn't support polymorphism; you need to give it a set of
template structures describing the expected shape of the incoming
JSON, and it will error out if that shape is not matched. When the
parse succeeds, attribute values will be extracted into static
locations specified in the template structures.
The "shape" of a JSON object in the type signature of its
attributes (and attribute values, and so on recursively down through
all nestings of objects and arrays). This parser is indifferent to
the order of attributes at any level, but you have to tell it in
advance what the type of each attribute value will be and where the
parsed value will be stored. The template structures may supply
default values to be used when an expected attribute is omitted.
The preceding paragraph told one fib. A single attribute may
actually have a span of multiple specifications with different
syntactically distinguishable types (e.g. string vs. real vs. integer
vs. boolean, but not signed integer vs. unsigned integer). The parser
will match the right spec against the actual data.
The dialect this parses has some limitations. First, it cannot
recognize the JSON "null" value. Second, all elements of an array must
be of the same type. Third, characters may not be array elements (this
restriction could be lifted)
There are separate entry points for beginning a parse of either
JSON object or a JSON array. JSON "float" quantities are actually
stored as doubles.
This parser processes object arrays in one of two different ways,
defending on whether the array subtype is declared as object or
structobject.
Object arrays take one base address per object subfield, and are
mapped into parallel C arrays (one per subfield). Strings are not
supported in this kind of array, as they don't have a "natural" size
to use as an offset multiplier.
Structobjects arrays are a way to parse a list of objects to a set
of modifications to a corresponding array of C structs. The trick is
that the array object initialization has to specify both the C struct
array's base address and the stride length (the size of the C struct).
If you initialize the offset fields with the correct offsetof calls,
everything will work. Strings are supported but all string storage
has to be inline in the struct.
PERMISSIONS
This file is Copyright (c) 2014 by Eric S. Raymond
SPDX-License-Identifier: BSD-2-Clause
***************************************************************************/
/* The strptime prototype is not provided unless explicitly requested.
* We also need to set the value high enough to signal inclusion of
* newer features (like clock_gettime). See the POSIX spec for more info:
* http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_02_01_02 */
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>
#include <math.h> /* for HUGE_VAL */
#include "mjson.h"
#define str_starts_with(s, p) (strncmp(s, p, strlen(p)) == 0)
#ifdef DEBUG_ENABLE
static int debuglevel = 0;
static FILE *debugfp;
void json_enable_debug(int level, FILE * fp)
/* control the level and destination of debug trace messages */
{
debuglevel = level;
debugfp = fp;
}
static void json_trace(int errlevel, const char *fmt, ...)
/* assemble command in printf(3) style */
{
if (errlevel <= debuglevel) {
char buf[BUFSIZ];
va_list ap;
(void)strncpy(buf, "json: ", BUFSIZ-1);
buf[BUFSIZ-1] = '\0';
va_start(ap, fmt);
(void)vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt,
ap);
va_end(ap);
(void)fputs(buf, debugfp);
}
}
# define json_debug_trace(args) (void) json_trace args
#else
# define json_debug_trace(args) do { } while (0)
#endif /* DEBUG_ENABLE */
static char *json_target_address(const struct json_attr_t *cursor,
const struct json_array_t
*parent, int offset)
{
char *targetaddr = NULL;
if (parent == NULL || parent->element_type != t_structobject) {
/* ordinary case - use the address in the cursor structure */
switch (cursor->type) {
case t_ignore:
targetaddr = NULL;
break;
case t_integer:
targetaddr = (char *)&cursor->addr.integer[offset];
break;
case t_uinteger:
targetaddr = (char *)&cursor->addr.uinteger[offset];
break;
case t_short:
targetaddr = (char *)&cursor->addr.shortint[offset];
break;
case t_ushort:
targetaddr = (char *)&cursor->addr.ushortint[offset];
break;
#ifdef TIME_ENABLE
case t_time:
#endif /* TIME_ENABLE */
case t_real:
targetaddr = (char *)&cursor->addr.real[offset];
break;
case t_string:
targetaddr = cursor->addr.string;
break;
case t_boolean:
targetaddr = (char *)&cursor->addr.boolean[offset];
break;
case t_character:
targetaddr = (char *)&cursor->addr.character[offset];
break;
default:
targetaddr = NULL;
break;
}
} else
/* tricky case - hacking a member in an array of structures */
targetaddr =
parent->arr.objects.base + (offset * parent->arr.objects.stride) +
cursor->addr.offset;
json_debug_trace((1, "Target address for %s (offset %d) is %p\n",
cursor->attribute, offset, targetaddr));
return targetaddr;
}
#ifdef TIME_ENABLE
static double iso8601_to_unix(char *isotime)
/* ISO8601 UTC to Unix UTC */
{
double usec;
struct tm tm;
char *dp = strptime(isotime, "%Y-%m-%dT%H:%M:%S", &tm);
if (dp == NULL)
return (double)HUGE_VAL;
if (*dp == '.')
usec = strtod(dp, NULL);
else
usec = 0;
return (double)timegm(&tm) + usec;
}
#endif /* TIME_ENABLE */
static int json_internal_read_object(const char *cp,
const struct json_attr_t *attrs,
const struct json_array_t *parent,
int offset,
const char **end)
{
enum
{ init, await_attr, in_attr, await_value, in_val_string,
in_escape, in_val_token, post_val, post_element
} state = 0;
#ifdef DEBUG_ENABLE
char *statenames[] = {
"init", "await_attr", "in_attr", "await_value", "in_val_string",
"in_escape", "in_val_token", "post_val", "post_element",
};
#endif /* DEBUG_ENABLE */
char attrbuf[JSON_ATTR_MAX + 1], *pattr = NULL;
char valbuf[JSON_VAL_MAX + 1], *pval = NULL;
bool value_quoted = false;
char uescape[5]; /* enough space for 4 hex digits and a NUL */
const struct json_attr_t *cursor;
int substatus, n, maxlen = 0;
unsigned int u;
const struct json_enum_t *mp;
char *lptr;
if (end != NULL)
*end = NULL; /* give it a well-defined value on parse failure */
/* stuff fields with defaults in case they're omitted in the JSON input */
for (cursor = attrs; cursor->attribute != NULL; cursor++)
if (!cursor->nodefault) {
lptr = json_target_address(cursor, parent, offset);
if (lptr != NULL)
switch (cursor->type) {
case t_integer:
memcpy(lptr, &cursor->dflt.integer, sizeof(int));
break;
case t_uinteger:
memcpy(lptr, &cursor->dflt.uinteger, sizeof(unsigned int));
break;
case t_short:
memcpy(lptr, &cursor->dflt.shortint, sizeof(short));
break;
case t_ushort:
memcpy(lptr, &cursor->dflt.ushortint,
sizeof(unsigned short));
break;
#ifdef TIME_ENABLE
case t_time:
#endif /* TIME_ENABLE */
case t_real:
memcpy(lptr, &cursor->dflt.real, sizeof(double));
break;
case t_string:
if (parent != NULL
&& parent->element_type != t_structobject
&& offset > 0)
return JSON_ERR_NOPARSTR;
lptr[0] = '\0';
break;
case t_boolean:
memcpy(lptr, &cursor->dflt.boolean, sizeof(bool));
break;
case t_character:
lptr[0] = cursor->dflt.character;
break;
case t_object: /* silences a compiler warning */
case t_structobject:
case t_array:
case t_check:
case t_ignore:
break;
}
}
json_debug_trace((1, "JSON parse of '%s' begins.\n", cp));
/* parse input JSON */
for (; *cp != '\0'; cp++) {
json_debug_trace((2, "State %-14s, looking at '%c' (%p)\n",
statenames[state], *cp, cp));
switch (state) {
case init:
if (isspace((unsigned char) *cp))
continue;
else if (*cp == '{')
state = await_attr;
else {
json_debug_trace((1,
"Non-WS when expecting object start.\n"));
if (end != NULL)
*end = cp;
return JSON_ERR_OBSTART;
}
break;
case await_attr:
if (isspace((unsigned char) *cp))
continue;
else if (*cp == '"') {
state = in_attr;
pattr = attrbuf;
if (end != NULL)
*end = cp;
} else if (*cp == '}')
break;
else {
json_debug_trace((1, "Non-WS when expecting attribute.\n"));
if (end != NULL)
*end = cp;
return JSON_ERR_ATTRSTART;
}
break;
case in_attr:
if (pattr == NULL)
/* don't update end here, leave at attribute start */
return JSON_ERR_NULLPTR;
if (*cp == '"') {
*pattr++ = '\0';
json_debug_trace((1, "Collected attribute name %s\n",
attrbuf));
for (cursor = attrs; cursor->attribute != NULL; cursor++) {
json_debug_trace((2, "Checking against %s\n",
cursor->attribute));
if (strcmp(cursor->attribute, attrbuf) == 0)
break;
if (strcmp(cursor->attribute, "") == 0 &&
cursor->type == t_ignore) {
break;
}
}
if (cursor->attribute == NULL) {
json_debug_trace((1,
"Unknown attribute name '%s'"
" (attributes begin with '%s').\n",
attrbuf, attrs->attribute));
/* don't update end here, leave at attribute start */
return JSON_ERR_BADATTR;
}
state = await_value;
if (cursor->type == t_string)
maxlen = (int)cursor->len - 1;
else if (cursor->type == t_check)
maxlen = (int)strlen(cursor->dflt.check);
#ifdef TIME_ENABLE
else if (cursor->type == t_time)
maxlen = JSON_VAL_MAX;
#endif /* TIME_ENABLE */
else if (cursor->type == t_ignore)
maxlen = JSON_VAL_MAX;
else if (cursor->map != NULL)
maxlen = (int)sizeof(valbuf) - 1;
pval = valbuf;
} else if (pattr >= attrbuf + JSON_ATTR_MAX - 1) {
json_debug_trace((1, "Attribute name too long.\n"));
/* don't update end here, leave at attribute start */
return JSON_ERR_ATTRLEN;
} else
*pattr++ = *cp;
break;
case await_value:
if (isspace((unsigned char) *cp) || *cp == ':')
continue;
else if (*cp == '[') {
if (cursor->type != t_array) {
json_debug_trace((1,
"Saw [ when not expecting array.\n"));
if (end != NULL)
*end = cp;
return JSON_ERR_NOARRAY;
}
substatus = json_read_array(cp, &cursor->addr.array, &cp);
if (substatus != 0)
return substatus;
state = post_element;
} else if (cursor->type == t_array) {
json_debug_trace((1,
"Array element was specified, but no [.\n"));
if (end != NULL)
*end = cp;
return JSON_ERR_NOBRAK;
} else if (*cp == '{') {
if (cursor->type != t_object) {
json_debug_trace((1,
"Saw { when not expecting object.\n"));
if (end != NULL)
*end = cp;
return JSON_ERR_NOARRAY;
}
substatus = json_read_object(cp, cursor->addr.attrs, &cp);
if (substatus != 0)
return substatus;
--cp; // last } will be re-consumed by cp++ at end of loop
state = post_element;
} else if (cursor->type == t_object) {
json_debug_trace((1,
"Object element was specified, but no {.\n"));
if (end != NULL)
*end = cp;
return JSON_ERR_NOCURLY;
} else if (*cp == '"') {
value_quoted = true;
state = in_val_string;
pval = valbuf;
} else {
value_quoted = false;
state = in_val_token;
pval = valbuf;
*pval++ = *cp;
}
break;
case in_val_string:
if (pval == NULL)
/* don't update end here, leave at value start */
return JSON_ERR_NULLPTR;
if (*cp == '\\')
state = in_escape;
else if (*cp == '"') {
*pval++ = '\0';
json_debug_trace((1, "Collected string value %s\n", valbuf));
state = post_val;
} else if (pval > valbuf + JSON_VAL_MAX - 1
|| pval > valbuf + maxlen) {
json_debug_trace((1, "String value too long.\n"));
/* don't update end here, leave at value start */
return JSON_ERR_STRLONG; /* */
} else
*pval++ = *cp;
break;
case in_escape:
if (pval == NULL)
/* don't update end here, leave at value start */
return JSON_ERR_NULLPTR;
else if (pval > valbuf + JSON_VAL_MAX - 1
|| pval > valbuf + maxlen) {
json_debug_trace((1, "String value too long.\n"));
/* don't update end here, leave at value start */
return JSON_ERR_STRLONG; /* */
}
switch (*cp) {
case 'b':
*pval++ = '\b';
break;
case 'f':
*pval++ = '\f';
break;
case 'n':
*pval++ = '\n';
break;
case 'r':
*pval++ = '\r';
break;
case 't':
*pval++ = '\t';
break;
case 'u':
cp++; /* skip the 'u' */
for (n = 0; n < 4 && isxdigit(*cp); n++)
uescape[n] = *cp++;
uescape[n] = '\0'; /* terminate */
--cp;
/* ECMA-404 says JSON \u must have 4 hex digits */
if ((4 != n) || (1 != sscanf(uescape, "%4x", &u))) {
return JSON_ERR_BADSTRING;
}
*pval++ = (unsigned char)u; /* truncate values above 0xff */
break;
default: /* handles double quote and solidus */
*pval++ = *cp;
break;
}
state = in_val_string;
break;
case in_val_token:
if (pval == NULL)
/* don't update end here, leave at value start */
return JSON_ERR_NULLPTR;
if (isspace((unsigned char) *cp) || *cp == ',' || *cp == '}') {
*pval = '\0';
json_debug_trace((1, "Collected token value %s.\n", valbuf));
state = post_val;
if (*cp == '}' || *cp == ',')
--cp;
} else if (pval > valbuf + JSON_VAL_MAX - 1) {
json_debug_trace((1, "Token value too long.\n"));
/* don't update end here, leave at value start */
return JSON_ERR_TOKLONG;
} else
*pval++ = *cp;
break;
case post_val:
// Ignore whitespace after either string or token values.
if (isspace(*cp)) {
while (*cp != '\0' && isspace((unsigned char) *cp)) {
++cp;
}
json_debug_trace((1, "Skipped trailing whitespace: value \"%s\"\n", valbuf));
}
/*
* We know that cursor points at the first spec matching
* the current attribute. We don't know that it's *the*
* correct spec; our dialect allows there to be any number
* of adjacent ones with the same attrname but different
* types. Here's where we try to seek forward for a
* matching type/attr pair if we're not looking at one.
*/
for (;;) {
int seeking = cursor->type;
if (value_quoted && (cursor->type == t_string))
break;
#ifdef TIME_ENABLE
if(value_quoted && (cursor->type == t_time))
break;
#endif /* TIME_ENABLE */
if ((strcmp(valbuf, "true")==0 || strcmp(valbuf, "false")==0
|| isdigit((unsigned char) valbuf[0]))
&& seeking == t_boolean)
break;
if (isdigit((unsigned char) valbuf[0])) {
bool decimal = strchr(valbuf, '.') != NULL;
if (decimal && seeking == t_real)
break;
if (!decimal && (seeking == t_integer
|| seeking == t_uinteger))
break;
}
if (cursor[1].attribute==NULL) /* out of possiblities */
break;
if (strcmp(cursor[1].attribute, attrbuf)!=0)
break;
++cursor;
}
if (value_quoted
&& (cursor->type != t_string && cursor->type != t_character
&& cursor->type != t_check
&& cursor->type != t_ignore && cursor->map == 0)) {
json_debug_trace((1, "Saw quoted value when expecting"
" non-string.\n"));
#ifdef TIME_ENABLE
if(value_quoted && cursor->type != t_time)
json_debug_trace((1, "Saw quoted value when expecting"
" non-string.\n"));
#endif /* TIME_ENABLE */
return JSON_ERR_QNONSTRING;
}
if (!value_quoted
&& (cursor->type == t_string || cursor->type == t_check || cursor->map != 0)) {
json_debug_trace((1, "Didn't see quoted value when expecting"
" string.\n"));
#ifdef TIME_ENABLE
if(!value_quoted && cursor->type == t_time)
json_debug_trace((1, "Didn't see quoted value when expecting"
" string.\n"));
#endif /* TIME_ENABLE */
return JSON_ERR_NONQSTRING;
}
if (cursor->map != 0) {
for (mp = cursor->map; mp->name != NULL; mp++)
if (strcmp(mp->name, valbuf) == 0) {
goto foundit;
}
json_debug_trace((1, "Invalid enumerated value string \"%s\".\n",
valbuf));
return JSON_ERR_BADENUM;
foundit:
(void)snprintf(valbuf, sizeof(valbuf), "%d", mp->value);
}
lptr = json_target_address(cursor, parent, offset);
if (lptr != NULL)
switch (cursor->type) {
case t_integer:
{
int tmp = atoi(valbuf);
memcpy(lptr, &tmp, sizeof(int));
}
break;
case t_uinteger:
{
unsigned int tmp = (unsigned int)atoi(valbuf);
memcpy(lptr, &tmp, sizeof(unsigned int));
}
break;
case t_short:
{
short tmp = atoi(valbuf);
memcpy(lptr, &tmp, sizeof(short));
}
break;
case t_ushort:
{
unsigned short tmp = (unsigned int)atoi(valbuf);
memcpy(lptr, &tmp, sizeof(unsigned short));
}
break;
#ifdef TIME_ENABLE
case t_time:
{
double tmp = iso8601_to_unix(valbuf);
memcpy(lptr, &tmp, sizeof(double));
}
#endif /* TIME_ENABLE */
break;
case t_real:
{
double tmp = atof(valbuf);
memcpy(lptr, &tmp, sizeof(double));
}
break;
case t_string:
if (parent != NULL
&& parent->element_type != t_structobject
&& offset > 0)
return JSON_ERR_NOPARSTR;
else {
size_t vl = strlen(valbuf), cl = cursor->len-1;
memset(lptr, '\0', cl);
memcpy(lptr, valbuf, vl < cl ? vl : cl);
}
break;
case t_boolean:
{
bool tmp = (strcmp(valbuf, "true") == 0 || strtol(valbuf, NULL, 0));
memcpy(lptr, &tmp, sizeof(bool));
}
break;
case t_character:
if (strlen(valbuf) > 1)
/* don't update end here, leave at value start */
return JSON_ERR_STRLONG;
else
lptr[0] = valbuf[0];
break;
case t_ignore: /* silences a compiler warning */
case t_object: /* silences a compiler warning */
case t_structobject:
case t_array:
break;
case t_check:
if (strcmp(cursor->dflt.check, valbuf) != 0) {
json_debug_trace((1, "Required attribute value %s"
" not present.\n",
cursor->dflt.check));
/* don't update end here, leave at start of attribute */
return JSON_ERR_CHECKFAIL;
}
break;
}
__attribute__ ((fallthrough));
case post_element:
if (isspace((unsigned char) *cp))
continue;
else if (*cp == ',')
state = await_attr;
else if (*cp == '}') {
++cp;
goto good_parse;
} else {
json_debug_trace((1, "Garbage while expecting comma or }\n"));
if (end != NULL)
*end = cp;
return JSON_ERR_BADTRAIL;
}
break;
}
}
good_parse:
/* in case there's another object following, consume trailing WS */
while (*cp != '\0' && isspace((unsigned char) *cp))
++cp;
if (end != NULL)
*end = cp;
json_debug_trace((1, "JSON parse ends.\n"));
return 0;
}
int json_read_array(const char *cp, const struct json_array_t *arr,
const char **end)
{
int substatus, offset, arrcount;
char *tp;
if (end != NULL)
*end = NULL; /* give it a well-defined value on parse failure */
json_debug_trace((1, "Entered json_read_array()\n"));
while (isspace((unsigned char) *cp))
cp++;
if (*cp != '[') {
json_debug_trace((1, "Didn't find expected array start\n"));
return JSON_ERR_ARRAYSTART;
} else
cp++;
tp = arr->arr.strings.store;
arrcount = 0;
/* Check for empty array */
while (isspace((unsigned char) *cp))
cp++;
if (*cp == ']')
goto breakout;
for (offset = 0; offset < arr->maxlen; offset++) {
char *ep = NULL;
json_debug_trace((1, "Looking at %s\n", cp));
switch (arr->element_type) {
case t_string:
if (isspace((unsigned char) *cp))
cp++;
if (*cp != '"')
return JSON_ERR_BADSTRING;
else
++cp;
arr->arr.strings.ptrs[offset] = tp;
for (; tp - arr->arr.strings.store < arr->arr.strings.storelen;
tp++)
if (*cp == '"') {
++cp;
*tp++ = '\0';
goto stringend;
} else if (*cp == '\0') {
json_debug_trace((1,
"Bad string syntax in string list.\n"));
return JSON_ERR_BADSTRING;
} else {
*tp = *cp++;
}
json_debug_trace((1, "Bad string syntax in string list.\n"));
return JSON_ERR_BADSTRING;
stringend:
break;
case t_object:
case t_structobject:
substatus =
json_internal_read_object(cp, arr->arr.objects.subtype, arr,
offset, &cp);
if (substatus != 0) {
if (end != NULL)
end = &cp;
return substatus;
}
break;
case t_integer:
arr->arr.integers.store[offset] = (int)strtol(cp, &ep, 0);
if (ep == cp)
return JSON_ERR_BADNUM;
else
cp = ep;
break;
case t_uinteger:
arr->arr.uintegers.store[offset] = (unsigned int)strtoul(cp,
&ep, 0);
if (ep == cp)
return JSON_ERR_BADNUM;
else
cp = ep;
break;
case t_short:
arr->arr.shorts.store[offset] = (short)strtol(cp, &ep, 0);
if (ep == cp)
return JSON_ERR_BADNUM;
else
cp = ep;
break;
case t_ushort:
arr->arr.ushorts.store[offset] = (unsigned short)strtol(cp, &ep, 0);
if (ep == cp)
return JSON_ERR_BADNUM;
else
cp = ep;
break;
#ifdef TIME_ENABLE
case t_time:
if (*cp != '"')
return JSON_ERR_BADSTRING;
else
++cp;
arr->arr.reals.store[offset] = iso8601_to_unix((char *)cp);
if (arr->arr.reals.store[offset] >= HUGE_VAL)
return JSON_ERR_BADNUM;
while (*cp && *cp != '"')
cp++;
if (*cp != '"')
return JSON_ERR_BADSTRING;
else
++cp;
break;
#endif /* TIME_ENABLE */
case t_real:
arr->arr.reals.store[offset] = strtod(cp, &ep);
if (ep == cp)
return JSON_ERR_BADNUM;
else
cp = ep;
break;
case t_boolean:
if (str_starts_with(cp, "true")) {
arr->arr.booleans.store[offset] = true;
cp += 4;
}
else if (str_starts_with(cp, "false")) {
arr->arr.booleans.store[offset] = false;
cp += 5;
} else {
int val = strtol(cp, &ep, 0);
if (ep == cp)
return JSON_ERR_BADNUM;
else {
arr->arr.booleans.store[offset] = (bool) val;
cp = ep;
}
}
break;
case t_character:
case t_array:
case t_check:
case t_ignore:
json_debug_trace((1, "Invalid array subtype.\n"));
return JSON_ERR_SUBTYPE;
}
arrcount++;
if (isspace((unsigned char) *cp))
cp++;
if (*cp == ']') {
json_debug_trace((1, "End of array found.\n"));
goto breakout;
} else if (*cp == ',')
cp++;
else {
json_debug_trace((1, "Bad trailing syntax on array.\n"));
return JSON_ERR_BADSUBTRAIL;
}
}
json_debug_trace((1, "Too many elements in array.\n"));
if (end != NULL)
*end = cp;
return JSON_ERR_SUBTOOLONG;
breakout:
if (arr->count != NULL)
*(arr->count) = arrcount;
if (end != NULL)
*end = cp;
json_debug_trace((1, "leaving json_read_array() with %d elements\n",
arrcount));
return 0;
}
int json_read_object(const char *cp, const struct json_attr_t *attrs,
const char **end)
{
int st;
json_debug_trace((1, "json_read_object() sees '%s'\n", cp));
st = json_internal_read_object(cp, attrs, NULL, 0, end);
return st;
}
const char *json_error_string(int err)
{
const char *errors[] = {
"unknown error while parsing JSON",
"non-whitespace when expecting object start",
"non-whitespace when expecting attribute start",
"unknown attribute name",
"attribute name too long",
"saw [ when not expecting array",
"array element specified, but no [",
"string value too long",
"token value too long",
"garbage while expecting comma or } or ]",
"didn't find expected array start",
"error while parsing object array",
"too many array elements",
"garbage while expecting array comma",
"unsupported array element type",
"error while string parsing",
"check attribute not matched",
"can't support strings in parallel arrays",
"invalid enumerated value",
"saw quoted value when expecting nonstring",
"didn't see quoted value when expecting string",
"other data conversion error",
"unexpected null value or attribute pointer",
"object element specified, but no {",
};
if (err <= 0 || err >= (int)(sizeof(errors) / sizeof(errors[0])))
return errors[0];
else
return errors[err];
}
/* end */

@ -0,0 +1,164 @@
/* Structures for JSON parsing using only fixed-extent memory
*
* This file is Copyright (c) 2010 by the GPSD project
* SPDX-License-Identifier: BSD-2-clause
*/
#ifndef MICROJSON_H_
#define MICROJSON_H_
#include <stdbool.h>
#include <stdio.h>
#include <ctype.h>
#ifdef TIME_ENABLE
#include <time.h>
#endif /* TIME_ENABLE */
typedef enum {t_integer, t_uinteger, t_real,
t_string, t_boolean, t_character,
#ifdef TIME_ENABLE
t_time,
#endif /* TIME_ENABLE */
t_object, t_structobject, t_array,
t_check, t_ignore,
t_short, t_ushort}
json_type;
struct json_enum_t {
char *name;
int value;
};
struct json_array_t {
json_type element_type;
union {
struct {
const struct json_attr_t *subtype;
char *base;
size_t stride;
} objects;
struct {
char **ptrs;
char *store;
int storelen;
} strings;
struct {
int *store;
} integers;
struct {
unsigned int *store;
} uintegers;
struct {
short *store;
} shorts;
struct {
unsigned short *store;
} ushorts;
struct {
double *store;
} reals;
struct {
bool *store;
} booleans;
} arr;
int *count, maxlen;
};
struct json_attr_t {
char *attribute;
json_type type;
union {
int *integer;
unsigned int *uinteger;
short *shortint;
unsigned short *ushortint;
double *real;
char *string;
bool *boolean;
char *character;
const struct json_attr_t *attrs;
const struct json_array_t array;
size_t offset;
} addr;
union {
int integer;
unsigned int uinteger;
short shortint;
unsigned short ushortint;
double real;
bool boolean;
char character;
char *check;
} dflt;
size_t len;
const struct json_enum_t *map;
bool nodefault;
};
#define JSON_ATTR_MAX 31 /* max chars in JSON attribute name */
#define JSON_VAL_MAX 512 /* max chars in JSON value part */
#ifdef __cplusplus
extern "C" {
#endif
int json_read_object(const char *, const struct json_attr_t *,
const char **);
int json_read_array(const char *, const struct json_array_t *,
const char **);
const char *json_error_string(int);
#ifdef TIME_ENABLE
extern time_t timegm(struct tm *tm);
#endif /* TIME_ENABLE */
void json_enable_debug(int, FILE *);
#ifdef __cplusplus
}
#endif
#define JSON_ERR_OBSTART 1 /* non-WS when expecting object start */
#define JSON_ERR_ATTRSTART 2 /* non-WS when expecting attrib start */
#define JSON_ERR_BADATTR 3 /* unknown attribute name */
#define JSON_ERR_ATTRLEN 4 /* attribute name too long */
#define JSON_ERR_NOARRAY 5 /* saw [ when not expecting array */
#define JSON_ERR_NOBRAK 6 /* array element specified, but no [ */
#define JSON_ERR_STRLONG 7 /* string value too long */
#define JSON_ERR_TOKLONG 8 /* token value too long */
#define JSON_ERR_BADTRAIL 9 /* garbage while expecting comma or } or ] */
#define JSON_ERR_ARRAYSTART 10 /* didn't find expected array start */
#define JSON_ERR_OBJARR 11 /* error while parsing object array */
#define JSON_ERR_SUBTOOLONG 12 /* too many array elements */
#define JSON_ERR_BADSUBTRAIL 13 /* garbage while expecting array comma */
#define JSON_ERR_SUBTYPE 14 /* unsupported array element type */
#define JSON_ERR_BADSTRING 15 /* error while string parsing */
#define JSON_ERR_CHECKFAIL 16 /* check attribute not matched */
#define JSON_ERR_NOPARSTR 17 /* can't support strings in parallel arrays */
#define JSON_ERR_BADENUM 18 /* invalid enumerated value */
#define JSON_ERR_QNONSTRING 19 /* saw quoted value when expecting nonstring */
#define JSON_ERR_NONQSTRING 19 /* didn't see quoted value when expecting string */
#define JSON_ERR_MISC 20 /* other data conversion error */
#define JSON_ERR_BADNUM 21 /* error while parsing a numerical argument */
#define JSON_ERR_NULLPTR 22 /* unexpected null value or attribute pointer */
#define JSON_ERR_NOCURLY 23 /* object element specified, but no { */
/*
* Use the following macros to declare template initializers for structobject
* arrays. Writing the equivalents out by hand is error-prone.
*
* STRUCTOBJECT takes a structure name s, and a fieldname f in s.
*
* STRUCTARRAY takes the name of a structure array, a pointer to a an
* initializer defining the subobject type, and the address of an integer to
* store the length in.
*/
#define STRUCTOBJECT(s, f) .addr.offset = offsetof(s, f)
#define STRUCTARRAY(a, e, n) \
.addr.array.element_type = t_structobject, \
.addr.array.arr.objects.subtype = e, \
.addr.array.arr.objects.base = (char*)a, \
.addr.array.arr.objects.stride = sizeof(a[0]), \
.addr.array.count = n, \
.addr.array.maxlen = (int)(sizeof(a)/sizeof(a[0]))
/* json.h ends here */
#endif /* MICROJSON_H_ */

@ -0,0 +1,978 @@
/* test_mjson.c - unit test for JSON parsing into fixed-extent structures
*
* This file is Copyright (c) 2014 by Eric S. Raymond
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <getopt.h>
#include <limits.h>
#include <math.h>
#include <ctype.h>
#include <stdbool.h>
#include <math.h>
#include <stddef.h>
#include "mjson.h"
/*
* Many of these structures and examples were dissected out of the GPSD code.
*/
#define MAXCHANNELS 20
#define MAXUSERDEVS 4
#define JSON_DATE_MAX 24 /* ISO8601 timestamp with 2 decimal places */
/* these values don't matter in themselves, they just have to be out-of-band */
#define DEVDEFAULT_BPS 0
#define DEVDEFAULT_PARITY 'X'
#define DEVDEFAULT_STOPBITS 3
#define DEVDEFAULT_NATIVE -1
typedef double timestamp_t; /* Unix time in seconds with fractional part */
struct dop_t {
/* Dilution of precision factors */
double xdop, ydop, pdop, hdop, vdop, tdop, gdop;
};
struct version_t {
char release[64]; /* external version */
char rev[64]; /* internal revision ID */
int proto_major, proto_minor; /* API major and minor versions */
char remote[PATH_MAX]; /* could be from a remote device */
};
struct devconfig_t {
char path[PATH_MAX];
int flags;
#define SEEN_GPS 0x01
#define SEEN_RTCM2 0x02
#define SEEN_RTCM3 0x04
#define SEEN_AIS 0x08
char driver[64];
char subtype[64];
double activated;
unsigned int baudrate, stopbits; /* RS232 link parameters */
char parity; /* 'N', 'O', or 'E' */
double cycle, mincycle; /* refresh cycle time in seconds */
int driver_mode; /* is driver in native mode or not? */
};
struct gps_fix_t {
timestamp_t time; /* Time of update */
int mode; /* Mode of fix */
#define MODE_NOT_SEEN 0 /* mode update not seen yet */
#define MODE_NO_FIX 1 /* none */
#define MODE_2D 2 /* good for latitude/longitude */
#define MODE_3D 3 /* good for altitude/climb too */
double ept; /* Expected time uncertainty */
double latitude; /* Latitude in degrees (valid if mode >= 2) */
double epy; /* Latitude position uncertainty, meters */
double longitude; /* Longitude in degrees (valid if mode >= 2) */
double epx; /* Longitude position uncertainty, meters */
double altitude; /* Altitude in meters (valid if mode == 3) */
double epv; /* Vertical position uncertainty, meters */
double track; /* Course made good (relative to true north) */
double epd; /* Track uncertainty, degrees */
double speed; /* Speed over ground, meters/sec */
double eps; /* Speed uncertainty, meters/sec */
double climb; /* Vertical speed, meters/sec */
double epc; /* Vertical speed uncertainty */
};
struct gps_data_t {
struct gps_fix_t fix; /* accumulated PVT data */
/* this should move to the per-driver structure */
double separation; /* Geoidal separation, MSL - WGS84 (Meters) */
/* GPS status -- always valid */
int status; /* Do we have a fix? */
#define STATUS_NO_FIX 0 /* no */
#define STATUS_FIX 1 /* yes, without DGPS */
#define STATUS_DGPS_FIX 2 /* yes, with DGPS */
/* precision of fix -- valid if satellites_used > 0 */
int satellites_used; /* Number of satellites used in solution */
int used[MAXCHANNELS]; /* PRNs of satellites used in solution */
struct dop_t dop;
/* redundant with the estimate elements in the fix structure */
double epe; /* spherical position error, 95% confidence (meters) */
/* satellite status -- valid when satellites_visible > 0 */
timestamp_t skyview_time; /* skyview timestamp */
int satellites_visible; /* # of satellites in view */
int PRN[MAXCHANNELS]; /* PRNs of satellite */
int elevation[MAXCHANNELS]; /* elevation of satellite */
int azimuth[MAXCHANNELS]; /* azimuth */
double ss[MAXCHANNELS]; /* signal-to-noise ratio (dB) */
struct devconfig_t dev; /* device that shipped last update */
struct {
timestamp_t time;
int ndevices;
struct devconfig_t list[MAXUSERDEVS];
} devices;
struct version_t version;
};
static struct gps_data_t gpsdata;
static int json_tpv_read(const char *buf, struct gps_data_t *gpsdata,
const char **endptr)
{
const struct json_attr_t json_attrs_1[] = {
/* *INDENT-OFF* */
{"class", t_check, .dflt.check = "TPV"},
{"device", t_string, .addr.string = gpsdata->dev.path,
.len = sizeof(gpsdata->dev.path)},
#ifdef TIME_ENABLE
{"time", t_time, .addr.real = &gpsdata->fix.time,
.dflt.real = NAN},
#else
{"time", t_ignore},
#endif /* TIME_ENABLE */
{"ept", t_real, .addr.real = &gpsdata->fix.ept,
.dflt.real = NAN},
{"lon", t_real, .addr.real = &gpsdata->fix.longitude,
.dflt.real = NAN},
{"lat", t_real, .addr.real = &gpsdata->fix.latitude,
.dflt.real = NAN},
{"alt", t_real, .addr.real = &gpsdata->fix.altitude,
.dflt.real = NAN},
{"epx", t_real, .addr.real = &gpsdata->fix.epx,
.dflt.real = NAN},
{"epy", t_real, .addr.real = &gpsdata->fix.epy,
.dflt.real = NAN},
{"epv", t_real, .addr.real = &gpsdata->fix.epv,
.dflt.real = NAN},
{"track", t_real, .addr.real = &gpsdata->fix.track,
.dflt.real = NAN},
{"speed", t_real, .addr.real = &gpsdata->fix.speed,
.dflt.real = NAN},
{"climb", t_real, .addr.real = &gpsdata->fix.climb,
.dflt.real = NAN},
{"epd", t_real, .addr.real = &gpsdata->fix.epd,
.dflt.real = NAN},
{"eps", t_real, .addr.real = &gpsdata->fix.eps,
.dflt.real = NAN},
{"epc", t_real, .addr.real = &gpsdata->fix.epc,
.dflt.real = NAN},
{"mode", t_integer, .addr.integer = &gpsdata->fix.mode,
.dflt.integer = MODE_NOT_SEEN},
{NULL},
/* *INDENT-ON* */
};
return json_read_object(buf, json_attrs_1, endptr);
}
static int json_sky_read(const char *buf, struct gps_data_t *gpsdata,
const char **endptr)
{
bool usedflags[MAXCHANNELS];
const struct json_attr_t json_attrs_2_1[] = {
/* *INDENT-OFF* */
{"PRN", t_integer, .addr.integer = gpsdata->PRN},
{"el", t_integer, .addr.integer = gpsdata->elevation},
{"az", t_integer, .addr.integer = gpsdata->azimuth},
{"ss", t_real, .addr.real = gpsdata->ss},
{"used", t_boolean, .addr.boolean = usedflags},
/* *INDENT-ON* */
{NULL},
};
const struct json_attr_t json_attrs_2[] = {
/* *INDENT-OFF* */
{"class", t_check, .dflt.check = "SKY"},
{"device", t_string, .addr.string = gpsdata->dev.path,
.len = sizeof(gpsdata->dev.path)},
{"hdop", t_real, .addr.real = &gpsdata->dop.hdop,
.dflt.real = NAN},
{"xdop", t_real, .addr.real = &gpsdata->dop.xdop,
.dflt.real = NAN},
{"ydop", t_real, .addr.real = &gpsdata->dop.ydop,
.dflt.real = NAN},
{"vdop", t_real, .addr.real = &gpsdata->dop.vdop,
.dflt.real = NAN},
{"tdop", t_real, .addr.real = &gpsdata->dop.tdop,
.dflt.real = NAN},
{"pdop", t_real, .addr.real = &gpsdata->dop.pdop,
.dflt.real = NAN},
{"gdop", t_real, .addr.real = &gpsdata->dop.gdop,
.dflt.real = NAN},
{"satellites", t_array, .addr.array.element_type = t_object,
.addr.array.arr.objects.subtype=json_attrs_2_1,
.addr.array.maxlen = MAXCHANNELS,
.addr.array.count = &gpsdata->satellites_visible},
{NULL},
/* *INDENT-ON* */
};
int status, i, j;
for (i = 0; i < MAXCHANNELS; i++) {
gpsdata->PRN[i] = 0;
usedflags[i] = false;
}
status = json_read_object(buf, json_attrs_2, endptr);
if (status != 0)
return status;
gpsdata->satellites_used = 0;
gpsdata->satellites_visible = 0;
(void)memset(gpsdata->used, '\0', sizeof(gpsdata->used));
for (i = j = 0; i < MAXCHANNELS; i++) {
if(gpsdata->PRN[i] > 0)
gpsdata->satellites_visible++;
if (usedflags[i]) {
gpsdata->used[j++] = gpsdata->PRN[i];
gpsdata->satellites_used++;
}
}
return 0;
}
static int json_devicelist_read(const char *buf, struct gps_data_t *gpsdata,
const char **endptr)
{
const struct json_attr_t json_attrs_subdevices[] = {
/* *INDENT-OFF* */
{"class", t_check, .dflt.check = "DEVICE"},
{"path", t_string, STRUCTOBJECT(struct devconfig_t, path),
.len = sizeof(gpsdata->devices.list[0].path)},
{"activated", t_real, STRUCTOBJECT(struct devconfig_t, activated)},
{"flags", t_integer, STRUCTOBJECT(struct devconfig_t, flags)},
{"driver", t_string, STRUCTOBJECT(struct devconfig_t, driver),
.len = sizeof(gpsdata->devices.list[0].driver)},
{"subtype", t_string, STRUCTOBJECT(struct devconfig_t, subtype),
.len = sizeof(gpsdata->devices.list[0].subtype)},
{"native", t_integer, STRUCTOBJECT(struct devconfig_t, driver_mode),
.dflt.integer = -1},
{"bps", t_uinteger, STRUCTOBJECT(struct devconfig_t, baudrate),
.dflt.uinteger = DEVDEFAULT_BPS},
{"parity", t_character, STRUCTOBJECT(struct devconfig_t, parity),
.dflt.character = DEVDEFAULT_PARITY},
{"stopbits", t_uinteger, STRUCTOBJECT(struct devconfig_t, stopbits),
.dflt.integer = DEVDEFAULT_STOPBITS},
{"cycle", t_real, STRUCTOBJECT(struct devconfig_t, cycle),
.dflt.real = NAN},
{"mincycle", t_real, STRUCTOBJECT(struct devconfig_t, mincycle),
.dflt.real = NAN},
{NULL},
/* *INDENT-ON* */
};
const struct json_attr_t json_attrs_devices[] = {
{"class", t_check,.dflt.check = "DEVICES"},
{"devices", t_array, STRUCTARRAY(gpsdata->devices.list,
json_attrs_subdevices,
&gpsdata->devices.ndevices)},
{NULL},
};
int status;
memset(&gpsdata->devices, '\0', sizeof(gpsdata->devices));
status = json_read_object(buf, json_attrs_devices, endptr);
if (status != 0) {
return status;
}
return 0;
}
static int json_device_read(const char *buf,
struct devconfig_t *dev,
const char **endptr)
{
char tbuf[JSON_DATE_MAX+1];
const struct json_attr_t json_attrs_device[] = {
{"class", t_check, .dflt.check = "DEVICE"},
{"path", t_string, .addr.string = dev->path,
.len = sizeof(dev->path)},
{"activated", t_string, .addr.string = tbuf,
.len = sizeof(tbuf)},
{"activated", t_real, .addr.real = &dev->activated},
{"flags", t_integer, .addr.integer = &dev->flags},
{"driver", t_string, .addr.string = dev->driver,
.len = sizeof(dev->driver)},
{"subtype", t_string, .addr.string = dev->subtype,
.len = sizeof(dev->subtype)},
{"native", t_integer, .addr.integer = &dev->driver_mode,
.dflt.integer = DEVDEFAULT_NATIVE},
{"bps", t_uinteger, .addr.uinteger = &dev->baudrate,
.dflt.uinteger = DEVDEFAULT_BPS},
{"parity", t_character, .addr.character = &dev->parity,
.dflt.character = DEVDEFAULT_PARITY},
{"stopbits", t_uinteger, .addr.uinteger = &dev->stopbits,
.dflt.uinteger = DEVDEFAULT_STOPBITS},
{"cycle", t_real, .addr.real = &dev->cycle,
.dflt.real = NAN},
{"mincycle", t_real, .addr.real = &dev->mincycle,
.dflt.real = NAN},
{NULL},
};
/* *INDENT-ON* */
int status;
tbuf[0] = '\0';
status = json_read_object(buf, json_attrs_device, endptr);
if (status != 0)
return status;
return 0;
}
static int json_version_read(const char *buf, struct gps_data_t *gpsdata,
const char **endptr)
{
const struct json_attr_t json_attrs_version[] = {
/* *INDENT-OFF* */
{"class", t_check, .dflt.check = "VERSION"},
{"release", t_string, .addr.string = gpsdata->version.release,
.len = sizeof(gpsdata->version.release)},
{"rev", t_string, .addr.string = gpsdata->version.rev,
.len = sizeof(gpsdata->version.rev)},
{"proto_major", t_integer, .addr.integer = &gpsdata->version.proto_major},
{"proto_minor", t_integer, .addr.integer = &gpsdata->version.proto_minor},
{"remote", t_string, .addr.string = gpsdata->version.remote,
.len = sizeof(gpsdata->version.remote)},
{NULL},
/* *INDENT-ON* */
};
int status;
memset(&gpsdata->version, '\0', sizeof(gpsdata->version));
status = json_read_object(buf, json_attrs_version, endptr);
return status;
}
static int libgps_json_unpack(const char *buf,
struct gps_data_t *gpsdata, const char **end)
/* the only entry point - unpack a JSON object into gpsdata_t substructures */
{
int status;
char *classtag = strstr(buf, "\"class\":");
if (classtag == NULL)
return -1;
#define STARTSWITH(str, prefix) strncmp(str, prefix, sizeof(prefix)-1)==0
if (STARTSWITH(classtag, "\"class\":\"TPV\"")) {
status = json_tpv_read(buf, gpsdata, end);
gpsdata->status = STATUS_FIX;
return status;
} else if (STARTSWITH(classtag, "\"class\":\"SKY\"")) {
status = json_sky_read(buf, gpsdata, end);
return status;
} else if (STARTSWITH(classtag, "\"class\":\"DEVICE\"")) {
status = json_device_read(buf, &gpsdata->dev, end);
return status;
} else if (STARTSWITH(classtag, "\"class\":\"DEVICES\"")) {
status = json_devicelist_read(buf, gpsdata, end);
return status;
} else if (STARTSWITH(classtag, "\"class\":\"VERSION\"")) {
status = json_version_read(buf, gpsdata, end);
return status;
}
#undef STARTSWITH
}
static int assert_error_case(int num, int status, int error)
{
if (status != error) {
(void)fprintf(stderr, "case %d FAILED, status %d (%s).\n", num,
status, json_error_string(status));
exit(EXIT_FAILURE);
}
else
return 0;
}
static void assert_case(int num, int status)
{
assert_error_case(num, status, 0);
}
static void assert_string(char *attr, char *fld, char *check)
{
if (strcmp(fld, check)) {
(void)fprintf(stderr,
"'%s' expecting string '%s', got '%s'.\n",
attr, check, fld);
exit(EXIT_FAILURE);
}
}
static void assert_integer(char *attr, int fld, int check)
{
if (fld != check) {
(void)fprintf(stderr,
"'%s' expecting integer %d, got %d.\n",
attr, check, fld);
exit(EXIT_FAILURE);
}
}
static void assert_uinteger(char *attr, unsigned int fld, unsigned int check)
{
if (fld != check) {
(void)fprintf(stderr,
"'%s' expecting uinteger %u, got %u.\n",
attr, check, fld);
exit(EXIT_FAILURE);
}
}
static void assert_boolean(char *attr, bool fld, bool check)
{
if (fld != check) {
(void)fprintf(stderr,
"'%s' expecting boolean %s, got %s.\n",
attr,
check ? "true" : "false",
fld ? "true" : "false");
exit(EXIT_FAILURE);
}
}
/*
* Floating point comparisons are iffy, but at least if any of these fail
* the output will make it clear whether it was a precision issue
*/
static void assert_real(char *attr, double fld, double check)
{
if (fld != check) {
(void)fprintf(stderr,
"'%s' expecting real %f got %f.\n",
attr, check, fld);
exit(EXIT_FAILURE);
}
}
/* Case 1: TPV report */
/* *INDENT-OFF* */
static const char json_str1[] = "{\"class\":\"TPV\",\
\"device\":\"GPS#1\", \
\"time\":\"2005-06-19T12:12:42.03Z\", \
\"lon\":46.498203637,\"lat\":7.568074350, \
\"alt\":1327.780,\"epx\":21.000,\"epy\":23.000,\"epv\":124.484,\"mode\":3}";
/* Case 2: SKY report */
static const char *json_str2 = "{\"class\":\"SKY\",\
\"satellites\":[\
{\"PRN\":10,\"el\":45,\"az\":196,\"ss\":34,\"used\":true},\
{\"PRN\":29,\"el\":67,\"az\":310,\"ss\":40,\"used\":true},\
{\"PRN\":28,\"el\":59,\"az\":108,\"ss\":42,\"used\":true},\
{\"PRN\":26,\"el\":51,\"az\":304,\"ss\":43,\"used\":true},\
{\"PRN\":8,\"el\":44,\"az\":58,\"ss\":41,\"used\":true},\
{\"PRN\":27,\"el\":16,\"az\":66,\"ss\":39,\"used\":true},\
{\"PRN\":21,\"el\":10,\"az\":301,\"ss\":0,\"used\":false}]}";
/* Case 3: String list syntax */
static const char *json_str3 = "[\"foo\",\"bar\",\"baz\"]";
static char *stringptrs[3];
static char stringstore[256];
static int stringcount;
static const struct json_array_t json_array_3 = {
.element_type = t_string,
.arr.strings.ptrs = stringptrs,
.arr.strings.store = stringstore,
.arr.strings.storelen = sizeof(stringstore),
.count = &stringcount,
.maxlen = sizeof(stringptrs)/sizeof(stringptrs[0]),
};
/* Case 4: test defaulting of unspecified attributes */
static const char *json_str4 = "{\"flag1\":true,\"flag2\":false}";
static bool flag1, flag2;
static double dftreal;
static int dftinteger;
static unsigned int dftuinteger;
static const struct json_attr_t json_attrs_4[] = {
{"dftint", t_integer, .addr.integer = &dftinteger, .dflt.integer = -5},
{"dftuint", t_integer, .addr.uinteger = &dftuinteger, .dflt.uinteger = 10},
{"dftreal", t_real, .addr.real = &dftreal, .dflt.real = 23.17},
{"flag1", t_boolean, .addr.boolean = &flag1,},
{"flag2", t_boolean, .addr.boolean = &flag2,},
{NULL},
};
/* Case 5: test DEVICE parsing */
static const char *json_str5 = "{\"class\":\"DEVICE\",\
\"path\":\"/dev/ttyUSB0\",\
\"flags\":5,\
\"driver\":\"Foonly\",\"subtype\":\"Foonly Frob\"\
}";
/* Case 6: test parsing of subobject list into array of structures */
static const char *json_str6 = "{\"parts\":[\
{\"name\":\"Urgle\", \"flag\":true, \"count\":3},\
{\"name\":\"Burgle\",\"flag\":false,\"count\":1},\
{\"name\":\"Witter\",\"flag\":true, \"count\":4},\
{\"name\":\"Thud\", \"flag\":false,\"count\":1}]}";
struct dumbstruct_t {
char name[64];
bool flag;
int count;
};
static struct dumbstruct_t dumbstruck[5];
static int dumbcount;
static const struct json_attr_t json_attrs_6_subtype[] = {
{"name", t_string, .addr.offset = offsetof(struct dumbstruct_t, name),
.len = 64},
{"flag", t_boolean, .addr.offset = offsetof(struct dumbstruct_t, flag),},
{"count", t_integer, .addr.offset = offsetof(struct dumbstruct_t, count),},
{NULL},
};
static const struct json_attr_t json_attrs_6[] = {
{"parts", t_array, .addr.array.element_type = t_structobject,
.addr.array.arr.objects.base = (char*)&dumbstruck,
.addr.array.arr.objects.stride = sizeof(struct dumbstruct_t),
.addr.array.arr.objects.subtype = json_attrs_6_subtype,
.addr.array.count = &dumbcount,
.addr.array.maxlen = sizeof(dumbstruck)/sizeof(dumbstruck[0])},
{NULL},
};
/* Case 7: test parsing of version response */
static const char *json_str7 = "{\"class\":\"VERSION\",\
\"release\":\"2.40dev\",\"rev\":\"dummy-revision\",\
\"proto_major\":3,\"proto_minor\":1}";
/* Case 8: test parsing arrays of enumerated types */
static const char *json_str8 = "{\"fee\":\"FOO\",\"fie\":\"BAR\",\"foe\":\"BAZ\"}";
static const struct json_enum_t enum_table[] = {
{"BAR", 6}, {"FOO", 3}, {"BAZ", 14}, {NULL}
};
static int fee, fie, foe;
static const struct json_attr_t json_attrs_8[] = {
{"fee", t_integer, .addr.integer = &fee, .map=enum_table},
{"fie", t_integer, .addr.integer = &fie, .map=enum_table},
{"foe", t_integer, .addr.integer = &foe, .map=enum_table},
{NULL},
};
/* Case 9: Like case 6 but w/ an empty array */
static const char *json_str9 = "{\"parts\":[]}";
/* Case 10: test buffer overflow of short string destination */
// static char json_strErr2[7 * JSON_VAL_MAX]; /* dynamically built */
static char *json_strOver = "{\"name\":\"\\u0033\\u0034\\u0035\\u0036\"}";
char json_short_string_dst[1];
static const struct json_attr_t json_short_string[] = {
{"name", t_string,
.addr.string = json_short_string_dst,
.len = sizeof(json_short_string_dst)},
{NULL},
};
/* Case 11: Read array of integers */
static const char *json_str11 = "[23,-17,5]";
static int intstore[4], intcount;
static const struct json_array_t json_array_11 = {
.element_type = t_integer,
.arr.integers.store = intstore,
.count = &intcount,
.maxlen = sizeof(intstore)/sizeof(intstore[0]),
};
/* Case 12: Read array of booleans */
static const char *json_str12 = "[true,false,true]";
static bool boolstore[4];
static int boolcount;
static const struct json_array_t json_array_12 = {
.element_type = t_boolean,
.arr.booleans.store = boolstore,
.count = &boolcount,
.maxlen = sizeof(boolstore)/sizeof(boolstore[0]),
};
/* Case 13: Read array of reals */
static const char *json_str13 = "[23.1,-17.2,5.3]";
static double realstore[4];
static int realcount;
static const struct json_array_t json_array_13 = {
.element_type = t_real,
.arr.reals.store = realstore,
.count = &realcount,
.maxlen = sizeof(realstore)/sizeof(realstore[0]),
};
/* Case 14: Read object within object. Test case not derived from GPSD */
static char json_inner_name_string_dst[JSON_VAL_MAX];
static int inner_value;
static const char *json_str14 = "{\"name\":\"wobble\",\"value\":{\"inner\":23}}";
const struct json_attr_t json_inner_int_value[] = {
{"inner", t_integer,
.addr.integer = &inner_value},
{NULL},
};
static const struct json_attr_t json_object_14[] = {
{"name", t_string,
.addr.string = json_inner_name_string_dst,
.len = sizeof(json_inner_name_string_dst)},
{"value", t_object,
.addr.attrs = json_inner_int_value},
{NULL},
};
/* Case 15: test parsing 0/1 integer values as bool */
static bool flag3;
static bool flags4[3];
static const char *json_str15 = "{\"flag1\":1, \"flag2\":0, \"flag3\":7, \"flags4\":[1,0,7]}";
static const struct json_attr_t json_attrs_15[] = {
{"flag1", t_boolean, .addr.boolean = &flag1,},
{"flag2", t_boolean, .addr.boolean = &flag2,},
{"flag3", t_boolean, .addr.boolean = &flag3,},
{"flags4", t_array, .addr.array.element_type = t_boolean,
.addr.array.arr.booleans = flags4,
.addr.array.maxlen = 3,},
{NULL},
};
/* Case 16: Read object within object within object.*/
char json_inner1_name_string_dst[JSON_VAL_MAX];
int inner1_value;
int inner2_value;
static const char *json_str16 = "{\"name\":\"wobble\",\"value\":{\"inner\":23, \"innerobject\":{\"innerinner\":123}}}\0READ PAST END OF BUFFER";
const struct json_attr_t json_inner2_int_value[] = {
{"innerinner", t_integer,
.addr.integer = &inner2_value},
{NULL},
};
const struct json_attr_t json_inner1_int_value[] = {
{"inner", t_integer,
.addr.integer = &inner1_value},
{"innerobject", t_object,
.addr.attrs = json_inner2_int_value},
{NULL},
};
static const struct json_attr_t json_object_16[] = {
{"name", t_string,
.addr.string = json_inner1_name_string_dst,
.len = sizeof(json_inner1_name_string_dst)},
{"value", t_object,
.addr.attrs = json_inner1_int_value},
{NULL},
};
/* Case 17: Parent struct enclosing enum in sub-struct followed by value outside of sub-struct */
static int eval, ival;
static const char *json_str17 = "{\"parentStruct\":{\"enumStruct\":{\"enumName\":\"NOT_SET\"\n},\"intName\":1}}";
static const struct json_enum_t enum_table16[] = {
{"NOT_SET", 0}, {"SET", 1},{NULL}
};
static const struct json_attr_t json_enum_struct[] = {
{"enumName", t_integer,.addr.integer = &eval,.map = enum_table16},
{NULL},
};
static const struct json_attr_t json_sub_struct[] = {
{"enumStruct", t_object,.addr.attrs = json_enum_struct},
{"intName", t_integer,.addr.integer = &ival},
{NULL},
};
static const struct json_attr_t json_object_17[] = {
{"parentStruct", t_object,.addr.attrs = json_sub_struct},
{NULL},
};
/* Case 18: Call json_read_object() on concatenated JSON objects. */
static const char *json_cur18;
static const char *json_end18;
static const char *json_str18 = "{\"flag1\":1}{\"flag1\":0}{\"flag1\":7, \"flags4\":[1,0,7]} {\"flag2\":true, \"flags4\":[0,true,false]}";
/* Insert more test definitions here */
/* *INDENT-ON* */
static void jsontest(int i)
{
int status = 0;
switch (i)
{
case 1:
status = libgps_json_unpack(json_str1, &gpsdata, NULL);
assert_case(i, status);
assert_string("device", gpsdata.dev.path, "GPS#1");
#ifdef TIME_ENABLE
assert_real("time", gpsdata.fix.time, 1119183162.030000);
#endif /* TIME_ENABLE */
assert_integer("mode", gpsdata.fix.mode, 3);
assert_real("lon", gpsdata.fix.longitude, 46.498203637);
assert_real("lat", gpsdata.fix.latitude, 7.568074350);
break;
case 2:
status = libgps_json_unpack(json_str2, &gpsdata, NULL);
assert_case(i, status);
assert_integer("used", gpsdata.satellites_used, 6);
assert_integer("PRN[0]", gpsdata.PRN[0], 10);
assert_integer("el[0]", gpsdata.elevation[0], 45);
assert_integer("az[0]", gpsdata.azimuth[0], 196);
assert_real("ss[0]", gpsdata.ss[0], 34);
assert_integer("used[0]", gpsdata.used[0], 10);
assert_integer("used[5]", gpsdata.used[5], 27);
assert_integer("PRN[6]", gpsdata.PRN[6], 21);
assert_integer("el[6]", gpsdata.elevation[6], 10);
assert_integer("az[6]", gpsdata.azimuth[6], 301);
assert_real("ss[6]", gpsdata.ss[6], 0);
break;
case 3:
status = json_read_array(json_str3, &json_array_3, NULL);
assert_case(i, status);
assert(stringcount == 3);
assert(strcmp(stringptrs[0], "foo") == 0);
assert(strcmp(stringptrs[1], "bar") == 0);
assert(strcmp(stringptrs[2], "baz") == 0);
break;
case 4:
status = json_read_object(json_str4, json_attrs_4, NULL);
assert_case(i, status);
assert_integer("dftint", dftinteger, -5); /* did the default work? */
assert_uinteger("dftuint", dftuinteger, 10); /* did the default work? */
assert_real("dftreal", dftreal, 23.17); /* did the default work? */
assert_boolean("flag1", flag1, true);
assert_boolean("flag2", flag2, false);
break;
case 5:
status = libgps_json_unpack(json_str5, &gpsdata, NULL);
assert_case(i, status);
assert_string("path", gpsdata.dev.path, "/dev/ttyUSB0");
assert_integer("flags", gpsdata.dev.flags, 5);
assert_string("driver", gpsdata.dev.driver, "Foonly");
break;
case 6:
status = json_read_object(json_str6, json_attrs_6, NULL);
assert_case(i, status);
assert_integer("dumbcount", dumbcount, 4);
assert_string("dumbstruck[0].name", dumbstruck[0].name, "Urgle");
assert_string("dumbstruck[1].name", dumbstruck[1].name, "Burgle");
assert_string("dumbstruck[2].name", dumbstruck[2].name, "Witter");
assert_string("dumbstruck[3].name", dumbstruck[3].name, "Thud");
assert_boolean("dumbstruck[0].flag", dumbstruck[0].flag, true);
assert_boolean("dumbstruck[1].flag", dumbstruck[1].flag, false);
assert_boolean("dumbstruck[2].flag", dumbstruck[2].flag, true);
assert_boolean("dumbstruck[3].flag", dumbstruck[3].flag, false);
assert_integer("dumbstruck[0].count", dumbstruck[0].count, 3);
assert_integer("dumbstruck[1].count", dumbstruck[1].count, 1);
assert_integer("dumbstruck[2].count", dumbstruck[2].count, 4);
assert_integer("dumbstruck[3].count", dumbstruck[3].count, 1);
break;
case 7:
status = libgps_json_unpack(json_str7, &gpsdata, NULL);
assert_case(i, status);
assert_string("release", gpsdata.version.release, "2.40dev");
assert_string("rev", gpsdata.version.rev, "dummy-revision");
assert_integer("proto_major", gpsdata.version.proto_major, 3);
assert_integer("proto_minor", gpsdata.version.proto_minor, 1);
break;
case 8:
status = json_read_object(json_str8, json_attrs_8, NULL);
assert_case(i, status);
assert_integer("fee", fee, 3);
assert_integer("fie", fie, 6);
assert_integer("foe", foe, 14);
break;
case 9:
/* yes, the '6' in the next line is correct */
status = json_read_object(json_str9, json_attrs_6, NULL);
assert_case(i, status);
assert_integer("dumbcount", dumbcount, 0);
break;
case 10:
status = json_read_object(json_strOver, json_short_string, NULL);
status = assert_error_case(10, status, JSON_ERR_STRLONG);
assert_string("name", json_short_string_dst, "");
break;
case 11:
status = json_read_array(json_str11, &json_array_11, NULL);
assert_integer("count", intcount, 3);
assert_integer("intstore[0]", intstore[0], 23);
assert_integer("intstore[1]", intstore[1], -17);
assert_integer("intstore[2]", intstore[2], 5);
assert_integer("intstore[3]", intstore[3], 0);
break;
case 12:
status = json_read_array(json_str12, &json_array_12, NULL);
assert_integer("count", boolcount, 3);
assert_boolean("boolstore[0]", boolstore[0], true);
assert_boolean("boolstore[1]", boolstore[1], false);
assert_boolean("boolstore[2]", boolstore[2], true);
assert_boolean("boolstore[3]", boolstore[3], false);
break;
case 13:
status = json_read_array(json_str13, &json_array_13, NULL);
assert_integer("count", realcount, 3);
assert_real("realstore[0]", realstore[0], 23.1);
assert_real("realstore[1]", realstore[1], -17.2);
assert_real("realstore[2]", realstore[2], 5.3);
assert_real("realstore[3]", realstore[3], 0);
break;
case 14:
status = json_read_object(json_str14, json_object_14, NULL);
assert_case(i, status);
assert_integer("inner", inner_value, 23);
assert_string("name", json_inner_name_string_dst, "wobble");
break;
case 15:
status = json_read_object(json_str15, json_attrs_15, NULL);
assert_case(i, status);
assert_boolean("flag1", flag1, true);
assert_boolean("flag2", flag2, false);
assert_boolean("flag3", flag3, true);
assert_boolean("flags4[0]", flags4[0], true);
assert_boolean("flags4[1]", flags4[1], false);
assert_boolean("flags4[2]", flags4[2], true);
break;
case 16:
status = json_read_object(json_str16, json_object_16, NULL);
assert_case(i, status);
assert_integer("inner", inner1_value, 23);
assert_integer("innerinner", inner2_value, 123);
assert_string("name", json_inner1_name_string_dst, "wobble");
break;
case 17:
status = json_read_object(json_str17, json_object_17, NULL);
assert_case(i, status);
assert_integer("ival", ival, 1);
assert_integer("eval", eval, 0);
break;
case 18:
json_cur18 = json_str18;
json_end18 = json_str18 + strlen(json_str18);
/* first JSON message */
status = json_read_object(json_cur18, json_attrs_15, &json_cur18);
assert_case(i, status);
assert_boolean("flag1", flag1, true);
/* second JSON message */
status = json_read_object(json_cur18, json_attrs_15, &json_cur18);
assert_case(i, status);
assert_boolean("flag1", flag1, false);
/* third JSON message */
status = json_read_object(json_cur18, json_attrs_15, &json_cur18);
assert_case(i, status);
assert_boolean("flag1", flag1, true);
assert_boolean("flags4[0]", flags4[0], true);
assert_boolean("flags4[1]", flags4[1], false);
assert_boolean("flags4[2]", flags4[2], true);
/* fourth JSON message */
status = json_read_object(json_cur18, json_attrs_15, &json_cur18);
assert_case(i, status);
assert_boolean("flag2", flag2, true);
assert_boolean("flags4[0]", flags4[0], false);
assert_boolean("flags4[1]", flags4[1], true);
assert_boolean("flags4[2]", flags4[2], false);
break;
#define MAXTEST 18
default:
(void)fputs("Unknown test number\n", stderr);
break;
}
if (status > 0)
printf("Parse failure!\n");
}
int main(int argc, char *argv[])
{
int option;
int individual = 0;
while ((option = getopt(argc, argv, "hn:D:?")) != -1) {
switch (option) {
case 'D':
#ifdef DEBUG_ENABLE
json_enable_debug(atoi(optarg), stdout);
#else
fputs("Debug disabled in build\n", stderr);
#endif
break;
case 'n':
individual = atoi(optarg);
break;
case '?':
case 'h':
default:
(void)fputs("usage: test_json [-D lvl]\n", stderr);
exit(EXIT_FAILURE);
}
}
(void)fprintf(stderr, "microjson unit test ");
if (individual)
jsontest(individual);
else {
int i;
for (i = 1; i <= MAXTEST; i++)
jsontest(i);
}
(void)fprintf(stderr, "succeeded.\n");
exit(EXIT_SUCCESS);
}
/* end */

@ -0,0 +1,117 @@
//#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mjson.h"
static int test_ver(void);
static int test_watch(void);
static int test_tpv(void);
int main(void);
static char *VER =
"{\"class\":\"VERSION\",\"release\":\"3.19.1~dev\",\"rev\":\"release-3.19-"
"655-gb4aded4c1\",\"proto_major\":3,\"proto_minor\":14}";
static char *WAT =
"{\"class\":\"WATCH\",\"enable\":true,\"json\":true,\"nmea\":false,\"raw\":"
"0,\"scaled\":false,\"timing\":false,\"split24\":false,\"pps\":false,"
"\"device\":\"/dev/ttyUSB0\"}";
static char *TPV =
"{\"class\":\"TPV\",\"device\":\"/dev/"
"ttyUSB0\",\"mode\":3,\"time\":\"2019-10-04T08:51:34.000Z\",\"ept\":0.005,"
"\"lat\":46.367303831,\"lon\":-116.963791235,\"altHAE\":460.834,\"altMSL\":"
"476.140,\"epx\":7.842,\"epy\":12.231,\"epv\":30.607,\"track\":57.1020,"
"\"magtrack\":70.9299,\"magvar\":13.8,\"speed\":0.065,\"climb\":-0.206,"
"\"eps\":24.46,\"epc\":61.21,\"ecefx\":-1999242.00,\"ecefy\":-3929871.00,"
"\"ecefz\":4593848.00,\"ecefvx\":0.12,\"ecefvy\":0.12,\"ecefvz\":-0.12,"
"\"velN\":0.035,\"velE\":0.055,\"velD\":0.206,\"geoidSep\":-15.307,\"eph\":"
"15.200,\"sep\":31.273}";
#define icmp(want, got) \
if (want != got) { \
tally++; \
printf("wanted %d got %d\n", want, got); \
}
#define fcmp(want, got, tol) \
if (fabs(want - got) > tol) { \
tally++; \
printf("wanted %f got %f diff %f > %f\n", want, got, (want-got), tol); \
}
#define scmp(want, got) \
if ((NULL == &got)||(0 != strcmp(want, got))) { \
tally++; \
printf("wanted '%s' got '%s'\n", want, got); \
}
#define fin() icmp(0, errno) return tally;
static int test_ver() {
int tally = 0;
char revision[50];
uint16_t pvhi, pvlo;
const struct json_attr_t json_attrs_version[] = {
{"class", t_check, .dflt.check = "VERSION"},
{"rev", t_string, .addr.string = (char *)&revision, .len = 50},
{"proto_major", t_ushort, .addr.ushortint = &pvhi},
{"proto_minor", t_ushort, .addr.ushortint = &pvlo},
{"", t_ignore},
{NULL},
};
printf(".");
errno = json_read_object(VER, json_attrs_version, NULL);
icmp(3, pvhi);
icmp(14, pvlo);
fin();
}
static int test_watch() {
int tally = 0;
bool enable, json;
const struct json_attr_t json_attrs_watch[] = {
{"class", t_check, .dflt.check = "WATCH"},
{"device", t_check, .dflt.check = "/dev/ttyUSB0"},
{"enable", t_boolean, .addr.boolean = &enable},
{"json", t_boolean, .addr.boolean = &json},
{"", t_ignore},
{NULL},
};
printf(".");
errno = json_read_object(WAT, json_attrs_watch, NULL);
icmp(true, enable);
icmp(true, json);
fin();
}
static int test_tpv() {
int tally = 0;
int gps_mode;
double ept;
char gps_time[50];
const struct json_attr_t json_attrs_tpv[] = {
{"class", t_check, .dflt.check = "TPV"},
{"device", t_check, .dflt.check = "/dev/ttyUSB0"},
{"mode", t_integer, .addr.integer = &gps_mode, .dflt.integer = -1},
{"time", t_string, .addr.string = (char *)&gps_time, .len = 50},
{"ept", t_real, .addr.real = &ept, .dflt.real = NAN},
{"", t_ignore},
{NULL},
};
printf(".");
errno = json_read_object(TPV, json_attrs_tpv, NULL);
icmp(3, gps_mode) fcmp(0.005, ept, 0.001);
scmp("2019-10-04T08:51:34.000Z", gps_time);
fin();
}
int main() {
int count = 0;
count += test_ver();
count += test_watch();
count += test_tpv();
if (count) {
printf("OOPS: %d\n", count);
}
return count;
}

@ -17,7 +17,6 @@ idf_component_register(
"sntp_time.c" "sntp_time.c"
"robot.c" "robot.c"
"myMqtt.c" "myMqtt.c"
"application.c"
"challenge_app.c" "challenge_app.c"
"splitflap_wrapper.c" "splitflap_wrapper.c"
"challenge_com.c" "challenge_com.c"

@ -79,7 +79,7 @@ static const McuShell_ParseCommandCallback CmdParserTable[] =
LED_ParseCommand, LED_ParseCommand,
#endif #endif
#if PL_CONFIG_CHALLENGE_APP_ACTIVATED #if PL_CONFIG_CHALLENGE_APP_ACTIVATED
Challenge_ParseCommand, Challenge_ParseShellCommand,
#endif #endif
NULL /* Sentinel */ NULL /* Sentinel */
}; };

@ -6,12 +6,15 @@
*/ */
#include <stdbool.h>
#include "challenge_app.h" #include "challenge_app.h"
#include "platform.h" #include "platform.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "myMqtt.h" #include "myMqtt.h"
#include "wifi.h" #include "wifi.h"
#include "McuUtility.h"
#include "challenge_com.h" #include "challenge_com.h"
/* LOCAL Var */ /* LOCAL Var */
@ -29,24 +32,23 @@ static void appTask(void *pv){
}*/ }*/
bool myMqttInitSuccess = false; bool myMqttInitSuccess = false;
ESP_LOGI("Challenge application was started. Waiting for WiFi connection...."); ESP_LOGI(TAG, "Challenge application was started. Waiting for WiFi connection....");
while(WiFi_isConnected() == false){ while(WiFi_isConnected() == false){
vTaskDelay(pdMS_TO_TICKS(1000)); vTaskDelay(pdMS_TO_TICKS(1000));
} }
// wifi now connected // wifi now connected
ESP_LOGI("Challenge Application task detected that WiFi is connected."); ESP_LOGI(TAG, "Challenge Application task detected that WiFi is connected.");
// endless loop: always reconnect to MQTT when something fails // endless loop: always reconnect to MQTT when something fails
while(1){ while(1){
ESP_LOGI("MyMqtt Initializing...."); ESP_LOGI(TAG, "MyMqtt Initializing....");
myMqttInitSuccess = MyMqtt_Init(); myMqttInitSuccess = MyMqtt_Init();
if(myMqttInitSuccess){ if(myMqttInitSuccess){
ESP_LOGI("MyMqtt Initialization successful."); ESP_LOGI(TAG, "MyMqtt Initialization successful.");
ESP_LOGI("Challenge Application started."); ESP_LOGI(TAG, "Challenge Application started.");
Challenge_Com_SubscribeToAllTopics();
MyMqtt_Subscribe(MQTT_TOPIC_SF_INITALL);
MyMqtt_Subscribe(MQTT_TOPIC_SF_DISPLAY);
MyMqtt_Subscribe(MQTT_TOPIC_SF_CONFIG_SETUP);
MyMqtt_Subscribe("scada/status"); MyMqtt_Subscribe("scada/status");
// endless loop // endless loop
@ -57,9 +59,10 @@ static void appTask(void *pv){
} }
} }
ESP_LOGE(myMqttInitSuccess?"Something in challenge app failed. Re-initializing MQTT in 5s.":"Init of MyMqtt failed! Retrying in 5s."); // TODO LOG
//ESP_LOGE(TAG, myMqttInitSuccess?(unsigned char*)"Something in challenge app failed. Re-initializing MQTT in 5s.":(unsigned char*)"Init of MyMqtt failed! Retrying in 5s.");
MyMqtt_Deinit(); MyMqtt_Deinit();
if(myMqttInitSuccess == false) vTaskDelay(5000); // only wait if the init is the issue if(myMqttInitSuccess == false) { vTaskDelay(5000); } // only wait if the init is the issue
} }
} }
@ -97,15 +100,14 @@ static uint8_t PrintHelp(const McuShell_StdIOType *io) {
return ERR_OK; return ERR_OK;
} }
uint8_t Challenge_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io) { uint8_t Challenge_ParseShellCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io) {
if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"challenge help")==0) { if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"challenge help")==0) {
*handled = TRUE; *handled = TRUE;
return PrintHelp(io); return PrintHelp(io);
} else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"challenge status")==0) { } else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"challenge status")==0) {
*handled = TRUE; *handled = TRUE;
return PrintStatus(io); return PrintStatus(io);
} } else if (McuUtility_strcmp((char*)cmd, (char*)"challenge setMode stationary")==0 ||
else if (McuUtility_strcmp((char*)cmd, (char*)"challenge setMode stationary")==0 ||
McuUtility_strcmp((char*)cmd, (char*)"challenge setMode s")==0) { McuUtility_strcmp((char*)cmd, (char*)"challenge setMode s")==0) {
*handled = TRUE; *handled = TRUE;
Challenge_App_SetRobotMode(true); // set stationary Challenge_App_SetRobotMode(true); // set stationary

@ -8,14 +8,14 @@
#ifndef MAIN_CHALLENGE_APP_H_ #ifndef MAIN_CHALLENGE_APP_H_
#define MAIN_CHALLENGE_APP_H_ #define MAIN_CHALLENGE_APP_H_
#include "stdbool.h" #include <stdbool.h>
/*! \brief Module initialization, start app task */ /*! \brief Module initialization, start app task */
void Challenge_App_Init(void); void Challenge_App_Init(void);
/*! \brief get mode of robot /*! \brief get mode of robot
* \return true if stationary, false if mobile */ * \return true if stationary, false if mobile */
bool Challenge_App_GetRobotMode(void){ bool Challenge_App_GetRobotMode(void);
#if PL_CONFIG_USE_SHELL #if PL_CONFIG_USE_SHELL
@ -28,7 +28,7 @@ bool Challenge_App_GetRobotMode(void){
* \param io I/O handler to be used * \param io I/O handler to be used
* \return error code, otherwise ERR_OK * \return error code, otherwise ERR_OK
*/ */
uint8_t Challenge_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io); uint8_t Challenge_ParseShellCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io);
#endif #endif
#endif /* MAIN_CHALLENGE_APP_H_ */ #endif /* MAIN_CHALLENGE_APP_H_ */

@ -5,12 +5,26 @@
* Author: jonas * Author: jonas
*/ */
#include "stdbool.h" #include <stdbool.h>
#include "challenge_com.h"
#include "challenge_app.h" #include "challenge_app.h"
#include "platform.h"
#include "esp_log.h"
#include "McuUtility.h"
#include "mjson.h"
#include "mqtt_client.h"
#include "myMqtt.h"
#include "splitflap_wrapper.h"
#define TAG "CHALLENGE_COM" /* tag for logging with ESP_LOG */ #define TAG "CHALLENGE_COM" /* tag for logging with ESP_LOG */
static void Challenge_Com_ParseMqtt(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data){ /* TOPICS */
const char MQTT_TOPIC_SF_DISPLAY[] = "/splitFlap/cmd/display/";
const char MQTT_TOPIC_SF_INITALL[] = "/splitFlap/cmd/init/";
const char MQTT_TOPIC_SF_CONFIG_SETUP[] = "/splitFlap/config/setup/";
void Challenge_Com_ParseMqtt(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data){
/* INFO /* INFO
* TOPIC: event->topic * TOPIC: event->topic
* TOPIC LENGTH: event->topic_len * TOPIC LENGTH: event->topic_len
@ -31,22 +45,49 @@ static void Challenge_Com_ParseMqtt(void *handler_args, esp_event_base_t base, i
/* check topic and call related command if allowed by robot mode */ /* check topic and call related command if allowed by robot mode */
/* stationary robot allowed commands */ /* stationary robot allowed commands */
if(Challenge_App_GetRobotMode() == true) { if(Challenge_App_GetRobotMode() == true) {
// CMD: CONFIG SETUP
if(McuUtility_strcmp((char*)event->topic, MQTT_TOPIC_SF_CONFIG_SETUP)==0){ if(McuUtility_strcmp((char*)event->topic, MQTT_TOPIC_SF_CONFIG_SETUP)==0){
handled = true; handled = true;
// TODO JSON PARSER // parse json
uint8_t setupId = 0; int setupId = 0;
uint8_t hwId = 1; int hwId = 0;
struct json_attr_t json_attrs[] = {
{"setupId", t_integer, .addr.integer = &setupId},
{"hardwareId", t_integer, .addr.integer = &hwId,},
{NULL},
};
if(json_read_object(event->data, json_attrs, NULL) != 0){
ESP_LOGE(TAG, "Parsing JSON data of CONFIG SETUP message failed. Event data was = %s", event->data);
} else{ // successfully parsed
// check values
if(setupId >= 0 && setupId <= 10 && hwId >= 0 && hwId <= 10){
SplitFlap_Wrapper_SetHardwareIdentifier(setupId, hwId); SplitFlap_Wrapper_SetHardwareIdentifier(setupId, hwId);
} else if(McuUtility_strcmp((char*)event->topic, MQTT_TOPIC_SF_INITALL)==0){ } else{
ESP_LOGE(TAG, "SetupID or HardwareID out of range. Event data was = %s", event->data);
}
}
}
// CMD: INIT ALL
else if(McuUtility_strcmp((char*)event->topic, MQTT_TOPIC_SF_INITALL)==0){
handled = true; handled = true;
SplitFlap_Wrapper_MoveAllToZeroPosition(); SplitFlap_Wrapper_MoveAllToZeroPosition();
} else if(McuUtility_strcmp((char*)event->topic, MQTT_TOPIC_SF_DISPLAY)==0){ }
// CMD: DISPLAY
else if(McuUtility_strcmp((char*)event->topic, MQTT_TOPIC_SF_DISPLAY)==0){
handled = true; handled = true;
// TODO JSON PARSER // parse json
char message[] = "TST"; char message[] = "TST";
struct json_attr_t json_attrs[] = {
{"message", t_string, .addr.string = message},
{NULL},
};
if(json_read_object(event->data, json_attrs, NULL) != 0){
ESP_LOGE(TAG, "Parsing JSON data of DISPLAY message failed. Event data was = %s", event->data);
} else{ // successfully parsed
SplitFlap_Wrapper_Display(message); SplitFlap_Wrapper_Display(message);
} }
} }
}
/* mobile robot allowed commands */ /* mobile robot allowed commands */
else{ else{
@ -56,11 +97,14 @@ static void Challenge_Com_ParseMqtt(void *handler_args, esp_event_base_t base, i
if(handled == false) if(handled == false){
{
ESP_LOGE(TAG, "Received data could not be handled. Topic was %s", event->topic); ESP_LOGE(TAG, "Received data could not be handled. Topic was %s", event->topic);
} }
return; return;
} }
void Challenge_Com_SubscribeToAllTopics(void){
MyMqtt_Subscribe(MQTT_TOPIC_SF_INITALL);
MyMqtt_Subscribe(MQTT_TOPIC_SF_DISPLAY);
MyMqtt_Subscribe(MQTT_TOPIC_SF_CONFIG_SETUP);
}

@ -8,12 +8,12 @@
#ifndef MAIN_CHALLENGE_COM_H_ #ifndef MAIN_CHALLENGE_COM_H_
#define MAIN_CHALLENGE_COM_H_ #define MAIN_CHALLENGE_COM_H_
/* TOPICS */ #include "mqtt_client.h"
const char MQTT_TOPIC_SF_DISPLAY[] = "/splitFlap/cmd/display/";
const char MQTT_TOPIC_SF_INITALL[] = "/splitFlap/cmd/init/";
const char MQTT_TOPIC_SF_CONFIG_SETUP[] = "/splitFlap/config/setup/";
/*! \brief parser for mqtt events */
void Challenge_Com_ParseMqtt(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data);
/*! \brief subscribes to all relevant topics */
void Challenge_Com_SubscribeToAllTopics(void);
#endif /* MAIN_CHALLENGE_COM_H_ */ #endif /* MAIN_CHALLENGE_COM_H_ */

@ -108,6 +108,6 @@ void PL_Init(void) {
ROBOT_Init(); ROBOT_Init();
#endif #endif
#if PL_CONFIG_CHALLENGE_APP_ACTIVATED #if PL_CONFIG_CHALLENGE_APP_ACTIVATED
Application_Start(); Challenge_App_Init();
#endif #endif
} }

@ -5,7 +5,10 @@
* Author: jonas * Author: jonas
*/ */
#include <stdint.h>
#include <stdbool.h>
#include "splitflap_wrapper.h" #include "splitflap_wrapper.h"
#include "McuUtility.h"
#include "rs485.h" #include "rs485.h"
#define RS_CMD_PREFIX "rs sendcmd SplitFlap " #define RS_CMD_PREFIX "rs sendcmd SplitFlap "
@ -17,8 +20,9 @@
* If all splitflaps report to be initialized before the timeout, the return value is true */ * If all splitflaps report to be initialized before the timeout, the return value is true */
bool SplitFlap_Wrapper_MoveAllToZeroPosition(void){ bool SplitFlap_Wrapper_MoveAllToZeroPosition(void){
char cmd[BUF_SIZE] = RS_CMD_PREFIX; char cmd[BUF_SIZE] = RS_CMD_PREFIX;
McuUtility_strcat(cmd, sizeof cmd, "initAll"); //McuUtility_strcat(cmd, sizeof cmd, "initAll");
RS485_ParseCommand(cmd); //RS485_ParseCommand(cmd); TODO
return true;
} }
/* display a sentence on the splitflap combination /* display a sentence on the splitflap combination
@ -26,9 +30,10 @@ bool SplitFlap_Wrapper_MoveAllToZeroPosition(void){
* returns true when all movements finished */ * returns true when all movements finished */
bool SplitFlap_Wrapper_Display(char sentence[]){ bool SplitFlap_Wrapper_Display(char sentence[]){
char cmd[BUF_SIZE] = RS_CMD_PREFIX; char cmd[BUF_SIZE] = RS_CMD_PREFIX;
McuUtility_strcat(cmd, sizeof cmd, "Display "); //McuUtility_strcat(cmd, sizeof cmd, "Display ");
McuUtility_strcat(cmd, sizeof cmd, sentence); //McuUtility_strcat(cmd, sizeof cmd, sentence);
RS485_ParseCommand(cmd); //RS485_ParseCommand(cmd); TODO
return true;
} }
/* sets the hardware identifier <hwId> of a splitflap with <id> in the combination /* sets the hardware identifier <hwId> of a splitflap with <id> in the combination
@ -36,11 +41,12 @@ bool SplitFlap_Wrapper_Display(char sentence[]){
bool SplitFlap_Wrapper_SetHardwareIdentifier(uint8_t id, uint8_t hwId){ bool SplitFlap_Wrapper_SetHardwareIdentifier(uint8_t id, uint8_t hwId){
char cmd[BUF_SIZE] = RS_CMD_PREFIX; char cmd[BUF_SIZE] = RS_CMD_PREFIX;
char setupId_str[4] = {0}; char hardwareId_str[4] = {0}; char setupId_str[4] = {0}; char hardwareId_str[4] = {0};
McuUtility_Num8uToStr(setupId_str, sizeof setupId_str, id); //McuUtility_Num8uToStr(setupId_str, sizeof setupId_str, id);
McuUtility_Num8uToStr(hardwareId_str, sizeof hardwareId_str, hwId); //McuUtility_Num8uToStr(hardwareId_str, sizeof hardwareId_str, hwId);
McuUtility_strcat(cmd, sizeof cmd, "setId "); //McuUtility_strcat(cmd, sizeof cmd, "setId ");
McuUtility_strcat(cmd, sizeof cmd, setupId_str); //McuUtility_strcat(cmd, sizeof cmd, setupId_str);
McuUtility_strcat(cmd, sizeof cmd, " "); //McuUtility_strcat(cmd, sizeof cmd, " ");
McuUtility_strcat(cmd, sizeof cmd, hardwareId_str); //McuUtility_strcat(cmd, sizeof cmd, hardwareId_str);
RS485_ParseCommand(cmd); //RS485_ParseCommand(cmd); TODO
return true;
} }

@ -8,6 +8,9 @@
#ifndef MAIN_SPLITFLAP_WRAPPER_H_ #ifndef MAIN_SPLITFLAP_WRAPPER_H_
#define MAIN_SPLITFLAP_WRAPPER_H_ #define MAIN_SPLITFLAP_WRAPPER_H_
#include <stdint.h>
#include <stdbool.h>
/* moves all split flaps to the zero position /* moves all split flaps to the zero position
* After a timeout the initializtion is cancelled and the return value is false * After a timeout the initializtion is cancelled and the return value is false
* If all splitflaps report to be initialized before the timeout, the return value is true */ * If all splitflaps report to be initialized before the timeout, the return value is true */

Loading…
Cancel
Save