1#!/usr/bin/env python3
2#
3# Copyright (C) 2017 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18# OBD2 standard sensor indices are different from those used by the
19# Android Auto Diagnostics API. This script maps from OBD2 sensors to
20# those expected by the Diagnostics API.
21# To use:
22# ./obd2_to_diagjson.py --src file1.json --dst file2.json
23# It is acceptable and supported to point --src and --dst to the same file
24
25import collections
26import json
27import os, os.path, sys
28
29class Json(object):
30    @classmethod
31    def load(cls, file):
32        return Json(json.load(file))
33
34    @classmethod
35    def wrapIfNeeded(cls, item):
36        if isinstance(item, list) or isinstance(item, dict):
37            return Json(item)
38        return item
39
40    def __init__(self, doc):
41        self.doc = doc
42
43    def __str__(self):
44        return str(self.doc)
45
46    def __repr__(self):
47        return self.__str__()
48
49    def __getattr__(self, attr):
50        return Json.wrapIfNeeded(self.doc.get(attr))
51
52    def __iter__(self):
53        class Iter(object):
54            def __init__(self, doc):
55                self.doc = doc.__iter__()
56
57            def __next__(self):
58                return Json.wrapIfNeeded(self.doc.__next__())
59
60        return Iter(self.doc)
61
62class OrderedStore(object):
63    def __init__(self):
64        self.__dict__['store'] = collections.OrderedDict()
65
66    def __setattr__(self, name, value):
67        self.__dict__['store'][name] = value
68
69    def __getattr__(self, name):
70        return self.__dict__['store'][name]
71
72    def get(self, name, default=None):
73        return self.__dict__['store'].get(name, default)
74
75    def getStore(self):
76        return self.__dict__['store']
77
78    def __iter__(self):
79        return iter(self.__dict__['store'])
80
81    def __delattr__(self, name):
82        del self.__dict__['store'][name]
83
84    def __str__(self):
85        return str(self.__dict__['store'])
86
87    def toJSON(self):
88        return json.dumps(self.store)
89
90class Event(object):
91    def __init__(self):
92        self.store = OrderedStore()
93
94    def setTimestamp(self, timestamp):
95        self.store.timestamp = timestamp
96        return self
97
98    def getTimestamp(self):
99        return self.store.timestamp
100
101    def setType(self, type):
102        self.store.type = type
103        return self
104
105    def getType(self):
106        return self.store.type
107
108    def setStringValue(self, string):
109        if string:
110            self.store.stringValue = string
111        return self
112
113    def getStringValue(self):
114        return self.store.get('stringValue')
115
116    def setIntValue(self, id, value):
117        if 'intValues' not in self.store:
118            self.store.intValues = []
119        d = collections.OrderedDict()
120        d['id'] = id
121        d['value'] = value
122        self.store.intValues.append(d)
123        return self
124
125    def intValues(self):
126        if 'intValues' not in self.store:
127            return []
128        for value in self.store.intValues:
129            yield (value['id'], value['value'])
130
131    def setFloatValue(self, id, value):
132        if 'floatValues' not in self.store:
133            self.store.floatValues = []
134        d = collections.OrderedDict()
135        d['id'] = id
136        d['value'] = value
137        self.store.floatValues.append(d)
138        return self
139
140    def floatValues(self):
141        if 'floatValues' not in self.store:
142            return []
143        for value in self.store.floatValues:
144            yield (value['id'], value['value'])
145
146    @classmethod
147    def fromJson(cls, json):
148        event = Event()
149        event.setTimestamp(json.timestamp)
150        event.setType(json.type)
151        for intValue in json.intValues:
152            event.setIntValue(intValue.id, intValue.value)
153        for floatValue in json.floatValues:
154            event.setFloatValue(floatValue.id, floatValue.value)
155        event.setStringValue(json.stringValue)
156        return event
157
158    def transform(self, intMapping, floatMapping):
159        event = Event()
160        event.setTimestamp(self.getTimestamp())
161        event.setType(self.getType())
162        for id, value in self.intValues():
163            if id in intMapping:
164                intMapping[id](event, value)
165            else:
166                print('warning: integer id 0x%x not found in mapping. dropped.' % id)
167        for id, value in self.floatValues():
168            if id in floatMapping:
169                floatMapping[id](event, value)
170            else:
171                print('warning: float id 0x%x not found in mapping. dropped.' % id)
172        event.setStringValue(self.getStringValue())
173        return event
174
175    def getStore(self):
176        return self.store.getStore()
177
178class EventEncoder(json.JSONEncoder):
179    def default(self, o):
180        if isinstance(o, Event):
181            return o.getStore()
182
183# Mappings between standard OBD2 sensors and the indices
184# used by Vehicle HAL
185intSensorsMapping = {
186    0x03 : lambda event,value: event.setIntValue(0, value),
187    0x05 : lambda event,value: event.setFloatValue(1, value),
188    0x0A : lambda event,value: event.setIntValue(22, value),
189    0x0C : lambda event,value: event.setFloatValue(8, value),
190    0x0D : lambda event,value: event.setFloatValue(9, value),
191    0x1F : lambda event,value: event.setIntValue(7, value),
192    0x5C : lambda event,value: event.setIntValue(23, value),
193}
194
195floatSensorsMapping = {
196    0x04 : lambda event, value: event.setFloatValue(0, value),
197    0x06 : lambda event, value: event.setFloatValue(2, value),
198    0x07 : lambda event, value: event.setFloatValue(3, value),
199    0x08 : lambda event, value: event.setFloatValue(4, value),
200    0x09 : lambda event, value: event.setFloatValue(5, value),
201    0x11 : lambda event, value: event.setFloatValue(12, value),
202    0x2F : lambda event, value: event.setFloatValue(42, value),
203    0x46 : lambda event, value: event.setIntValue(13, int(value)),
204}
205
206def parseOptions():
207    from argparse import ArgumentParser
208    parser = ArgumentParser(description='OBD2 to Diagnostics JSON Converter')
209    parser.add_argument('--src', '-S', dest='source_file',
210        help='The source file to convert from', required=True)
211    parser.add_argument('--dst', '-D', dest='destination_file',
212        help='The destination file to convert to', required=True)
213    return parser.parse_args()
214
215args = parseOptions()
216if not os.path.exists(args.source_file):
217    print('source file %s does not exist' % args.source_file)
218    sys.exit(1)
219
220source_json = Json.load(open(args.source_file))
221dest_events = []
222
223for source_json_event in source_json:
224    source_event = Event.fromJson(source_json_event)
225    destination_event = source_event.transform(intSensorsMapping, floatSensorsMapping)
226    dest_events.append(destination_event)
227
228json.dump(dest_events, open(args.destination_file, 'w'), cls=EventEncoder)
229