Skip to content

Commit 21d41b8

Browse files
authored
Merge pull request #12 from SoftwareAG/features/autocreate-profiles
- creates for every device one predefined profile if no profiles yet exist - use non-root user for Docker image - move OeeAPI to its own file - improve logging - fix minor issues
2 parents 7dab18f + 5e4a3db commit 21d41b8

File tree

8 files changed

+266
-204
lines changed

8 files changed

+266
-204
lines changed

event-based-simulators/.dockerignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
*.tar
2-
*.zip
2+
*.zip
3+
__pycache__

event-based-simulators/.vscode/launch.json

+26-8
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,50 @@
44
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
55
"version": "0.2.0",
66
"configurations": [
7+
{
8+
"name": "Python: Current File",
9+
"type": "python",
10+
"request": "launch",
11+
"program": "${file}",
12+
"envFile": "${workspaceFolder}/.vscode/.env",
13+
"cwd": "${workspaceFolder}/main/",
14+
"console": "integratedTerminal"
15+
},
716
{
817
"name": "create profiles",
918
"type": "python",
1019
"request": "launch",
11-
"program": "main/profile_generator.py",
20+
"program": "profile_generator.py",
1221
"console": "integratedTerminal",
1322
"envFile": "${workspaceFolder}/.vscode/.env",
14-
"args": ["--create-profiles"]
15-
},
23+
"cwd": "${workspaceFolder}/main/",
24+
"args": [
25+
"--create-profiles"
26+
]
27+
},
1628
{
1729
"name": "remove simulator profiles via OEE API",
1830
"type": "python",
1931
"request": "launch",
20-
"program": "main/profile_generator.py",
32+
"program": "profile_generator.py",
2133
"console": "integratedTerminal",
2234
"envFile": "${workspaceFolder}/.vscode/.env",
23-
"args": ["--remove-simulator-profiles-via-oee"]
24-
},
35+
"cwd": "${workspaceFolder}/main/",
36+
"args": [
37+
"--remove-simulator-profiles-via-oee"
38+
]
39+
},
2540
{
2641
"name": "delete simulator profiles",
2742
"type": "python",
2843
"request": "launch",
29-
"program": "main/profile_generator.py",
44+
"program": "profile_generator.py",
3045
"console": "integratedTerminal",
3146
"envFile": "${workspaceFolder}/.vscode/.env",
32-
"args": ["--delete-simulator-profiles"]
47+
"cwd": "${workspaceFolder}/main/",
48+
"args": [
49+
"--delete-simulator-profiles"
50+
]
3351
}
3452
]
3553
}

event-based-simulators/Dockerfile

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
FROM python:3.8.11-alpine3.14
22

3-
ADD main /
3+
RUN pip install requests \
4+
&& addgroup -S appgroup \
5+
&& adduser -S appuser -G appgroup
46

5-
RUN pip install requests
7+
ADD --chown=appuser:appgroup main /home/appuser
8+
WORKDIR /home/appuser
69

10+
USER appuser
711
CMD [ "python", "./event_based_simulators.py" ]

event-based-simulators/cumulocity.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"apiVersion": "1",
3-
"version": "1.0.8",
3+
"version": "1.0.26",
44
"provider": {
55
"name": "Viktor Tymoshenko (event-based simulators)"
66
},
@@ -15,7 +15,8 @@
1515
"ROLE_MEASUREMENT_READ",
1616
"ROLE_EVENT_READ",
1717
"ROLE_ALARM_READ",
18-
"ROLE_IDENTITY_READ"
18+
"ROLE_IDENTITY_READ",
19+
"ROLE_OEECONFIGURATOR_ADMIN"
1920
],
2021
"roles":[]
2122
}

event-based-simulators/main/cumulocityAPI.py

+19-18
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
false = False
2424
true = True
2525
######################
26+
log = logging.getLogger("C8yAPI")
2627

2728
OEE_DATA_MODEL_FIELD_NAME = "@com_adamos_oee_datamodel_MachineOEEConfiguration"
2829

@@ -33,11 +34,11 @@ class CumulocityAPI:
3334

3435
def __init__(self) -> None:
3536
self.mocking = MOCK_REQUESTS.lower() == 'true'
36-
logging.info(f'MOCK_REQUESTS: {self.mocking}')
37+
log.info(f'MOCK_REQUESTS: {self.mocking}')
3738

3839
def send_event(self, event):
3940
if self.mocking:
40-
print("mock: send event ", json.dumps(event), ' to ', C8Y_BASE + '/event/events')
41+
log.info(f"mock: send event {json.dumps(event)} to {C8Y_BASE}/event/events")
4142
return json.dumps({'response': 200})
4243
else:
4344
response = requests.post(C8Y_BASE + '/event/events', headers=C8Y_HEADERS, data=json.dumps(event))
@@ -49,19 +50,19 @@ def send_event(self, event):
4950

5051
def log_warning_on_bad_repsonse(self, response):
5152
if not response.ok:
52-
logging.warning(f'response status code is not ok: {response}, content: {response.text}')
53+
log.warning(f'response status code is not ok: {response}, content: {response.text}')
5354

5455
def get_or_create_device(self, sim_id, label):
5556
if self.mocking:
56-
print("mock: get or create device with external id", sim_id)
57+
log.info(f"mock: get or create device with external id {sim_id}")
5758
return sim_id
5859

5960
# Check if device already created
6061
return self.get_device_by_external_id(sim_id) or self.__create_device(sim_id, label)
6162

6263
def count_all_profiles(self):
6364
if self.mocking:
64-
print(f'mock: count_profiles()')
65+
log.info(f'mock: count_profiles()')
6566
return 5
6667

6768
request_query = f'{C8Y_BASE}/inventory/managedObjects/count?type={self.OEE_CALCULATION_PROFILE_TYPE}'
@@ -73,23 +74,23 @@ def count_profiles(self, device_id):
7374
''' count all profiles for the given device id.
7475
'''
7576
if self.mocking:
76-
print(f'mock: count_profiles(${device_id})')
77+
log.info(f'mock: count_profiles(${device_id})')
7778
return 10
7879
request_query = f'{C8Y_BASE}/inventory/managedObjects/count?type={self.OEE_CALCULATION_PROFILE_TYPE}&text={device_id}'
7980
response = requests.get(request_query, headers=C8Y_HEADERS)
8081
if response.ok:
8182
try:
8283
return int(response.text)
8384
except Exception as e:
84-
logging.warn(f'cannot convert "${response.text}" to number. exception: {e}')
85+
log.warn(f'cannot convert "${response.text}" to number. exception: {e}')
8586
return 0
8687
else:
8788
self.log_warning_on_bad_repsonse(response)
8889
return 0
8990

9091
def create_managed_object(self, fragment: str):
9192
if self.mocking:
92-
print(f'mock: create_managed_object()')
93+
log.info(f'mock: create_managed_object()')
9394
return {'id': '0'}
9495
response = requests.post(C8Y_BASE + '/inventory/managedObjects', headers=C8Y_HEADERS, data=fragment)
9596
if response.ok:
@@ -100,7 +101,7 @@ def create_managed_object(self, fragment: str):
100101

101102
def get_managed_object(self, id: str):
102103
if self.mocking:
103-
print(f'mock: get_managed_object()')
104+
log.info(f'mock: get_managed_object()')
104105
return {'id': '0'}
105106
response = requests.get(C8Y_BASE + f'/inventory/managedObjects/{id}', headers=C8Y_HEADERS)
106107
if response.ok:
@@ -111,7 +112,7 @@ def get_managed_object(self, id: str):
111112

112113
def delete_managed_object(self, id: str):
113114
if self.mocking:
114-
print(f'mock: delete_managed_object()')
115+
log.info(f'mock: delete_managed_object()')
115116
return {'id': '0'}
116117
response = requests.delete(C8Y_BASE + f'/inventory/managedObjects/{id}', headers=C8Y_HEADERS)
117118
if response.ok:
@@ -122,7 +123,7 @@ def delete_managed_object(self, id: str):
122123

123124
def update_managed_object(self, device_id, fragment):
124125
if self.mocking:
125-
print(f'mock: update_managed_object()')
126+
log.info(f'mock: update_managed_object()')
126127
return {'id': '0'}
127128

128129
response = requests.put(f'{C8Y_BASE}/inventory/managedObjects/{device_id}', headers=C8Y_HEADERS, data=fragment)
@@ -133,7 +134,7 @@ def update_managed_object(self, device_id, fragment):
133134

134135
def add_child_object(self, device_id: str, child_id: str):
135136
if self.mocking:
136-
print(f'mock: add_child_device()')
137+
log.info(f'mock: add_child_device()')
137138
return {'id': '0'}
138139

139140
data = {"managedObject": {"id": child_id}}
@@ -146,13 +147,13 @@ def add_child_object(self, device_id: str, child_id: str):
146147

147148
def find_simulators(self):
148149
if self.mocking:
149-
print(f'mock: find_simulators()')
150+
log.info(f'mock: find_simulators()')
150151
return []
151152
response = requests.get(f'{C8Y_BASE}/inventory/managedObjects?type={self.C8Y_SIMULATORS_GROUP}&fragmentType=c8y_IsDevice&pageSize=100', headers=C8Y_HEADERS)
152153
if response.ok:
153154
mangaged_objects = response.json()['managedObjects']
154155
return [mo['id'] for mo in mangaged_objects]
155-
logging.warning(f'Cannot find simulators: {response}, content:{response.text}')
156+
log.warning(f'Cannot find simulators: {response}, content:{response.text}')
156157
return []
157158

158159
def get_external_ids(self, device_ids):
@@ -168,13 +169,13 @@ def get_device_by_external_id(self, external_id):
168169
response = requests.get(f'{C8Y_BASE}/identity/externalIds/{self.C8Y_SIMULATORS_GROUP}/{external_id}', headers=C8Y_HEADERS)
169170
if response.ok:
170171
device_id = response.json()['managedObject']['id']
171-
logging.info(f'Device({device_id}) has been found by its external id "{self.C8Y_SIMULATORS_GROUP}/{external_id}".')
172+
log.info(f'Device({device_id}) has been found by its external id "{self.C8Y_SIMULATORS_GROUP}/{external_id}".')
172173
return device_id
173-
logging.warning(f'No device has been found for the external id "{self.C8Y_SIMULATORS_GROUP}/{external_id}".')
174+
log.warning(f'No device has been found for the external id "{self.C8Y_SIMULATORS_GROUP}/{external_id}".')
174175
return None
175176

176177
def __create_device(self, external_id, name):
177-
logging.info(f'Creating a new device with following external id "{self.C8Y_SIMULATORS_GROUP}/{external_id}"')
178+
log.info(f'Creating a new device with following external id "{self.C8Y_SIMULATORS_GROUP}/{external_id}"')
178179
device = {
179180
'name': name,
180181
'c8y_IsDevice': {},
@@ -183,7 +184,7 @@ def __create_device(self, external_id, name):
183184
device = self.create_managed_object(json.dumps(device))
184185
device_id = device['id']
185186
if device_id:
186-
logging.info(f'new device created({device_id})')
187+
log.info(f'new device created({device_id})')
187188
return self.__add_external_id(device_id, external_id)
188189
return device_id
189190

event-based-simulators/main/event_based_simulators.py

+22-16
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
from random import randint, uniform
44

55
from cumulocityAPI import C8Y_BASE, C8Y_TENANT, C8Y_USER, C8Y_PASSWORD, CumulocityAPI
6+
from oeeAPI import OeeAPI, ProfileCreateMode
67

7-
VERSION = '1.0.8'
8+
VERSION = '1.0.26'
89
def current_timestamp(format = "%Y-%m-%dT%H:%M:%S.%f"):
910
return datetime.utcnow().strftime(format)[:-3] + 'Z'
1011

11-
logging.basicConfig(level=logging.INFO)
12-
logging.info(os.environ)
13-
logging.info(f"version: {VERSION}")
14-
logging.info(f"started at {current_timestamp()}")
12+
logging.basicConfig(format='%(asctime)s %(name)s:%(message)s', level=logging.INFO)
13+
log = logging.getLogger("sims")
14+
log.info(f"version: {VERSION}")
15+
log.info(f"started at {current_timestamp()}")
1516

1617

1718
# JSON-PYTHON mapping, to get json.load() working
@@ -20,17 +21,18 @@ def current_timestamp(format = "%Y-%m-%dT%H:%M:%S.%f"):
2021
true = True
2122
######################
2223

23-
logging.info(C8Y_BASE)
24-
logging.info(C8Y_TENANT)
25-
logging.info(C8Y_USER)
26-
logging.info(C8Y_PASSWORD)
24+
log.info(C8Y_BASE)
25+
log.info(C8Y_TENANT)
26+
log.info(C8Y_USER)
27+
#log.info(C8Y_PASSWORD)
2728

2829
def try_event(probability: float):
2930
''' Returns True if event occurs.
3031
'''
3132
return uniform(0.0, 1.0) <= probability
3233

3334
cumulocityAPI = CumulocityAPI()
35+
oeeAPI = OeeAPI()
3436

3537
class Task:
3638
def __init__(self, start_in_seconds: int, run_block) -> None:
@@ -247,14 +249,14 @@ def __on_shutdown_event(self, event_definition, task):
247249
min_duration = event_definition.get("minDuration") or 0
248250
max_duration = event_definition.get("maxDuration") or 5
249251
duration = int(uniform(min_duration, max_duration) * 60)
250-
logging.info(f'shutdown {self.device_id} for the next {duration} seconds.')
252+
log.info(f'shutdown {self.device_id} for the next {duration} seconds.')
251253
task = self.create_one_time_task({}, duration, MachineSimulator.__on_machine_up_event)
252254
self.tasks.append(task)
253255

254256
def __on_machine_up_event(self, event_definition, task):
255257
self.__produce_pieces()
256258
self.shutdown = False
257-
logging.info(f'Device({self.device_id}) is up now.')
259+
log.info(f'Device({self.device_id}) is up now.')
258260

259261
def __send_following_event(self, event_definition, timestamp = None, extra_params = {}):
260262
if "followedBy" in event_definition:
@@ -268,9 +270,9 @@ def __send_following_event(self, event_definition, timestamp = None, extra_param
268270
followed_by_task.extra["timestamp"] = timestamp
269271
followed_by_task.extra.update(extra_params)
270272
self.tasks.append(followed_by_task)
271-
logging.debug(f'{self.device_id} task({id(followed_by_task)}) added: {json.dumps(followed_by_definition)}, tasks: {len(self.tasks)}')
273+
log.debug(f'{self.device_id} task({id(followed_by_task)}) added: {json.dumps(followed_by_definition)}, tasks: {len(self.tasks)}')
272274
else:
273-
logging.debug(f'{self.device_id} followedBy task missed. probability = {1 - followed_by_hits / this_hits} , def: {json.dumps(followed_by_definition)}')
275+
log.debug(f'{self.device_id} followedBy task missed. probability = {1 - followed_by_hits / this_hits} , def: {json.dumps(followed_by_definition)}')
274276

275277
def create_one_time_task(self, event_definition, start_in_seconds = 2, event_callback = None):
276278
callback = event_callback or MachineSimulator.event_mapping[event_definition["type"]]
@@ -281,7 +283,7 @@ def __execute_callback_and_remove_task(self, callback, event_definition, task):
281283
callback(self, event_definition, task)
282284
if task in self.tasks:
283285
self.tasks.remove(task)
284-
logging.debug(f'{self.device_id} task({id(task)}) removed: {json.dumps(event_definition)}, tasks: {len(self.tasks)}')
286+
log.debug(f'{self.device_id} task({id(task)}) removed: {json.dumps(event_definition)}, tasks: {len(self.tasks)}')
285287

286288
def tick(self):
287289
if not self.enabled: return
@@ -313,7 +315,7 @@ def __create_task(self, event_definition):
313315

314316
task = PeriodicTask(min_interval_in_seconds, max_interval_in_seconds, event_callback)
315317

316-
logging.debug(f'create periodic task for {event_definition["type"]} ({min_hits_per_hour}, {max_hits_per_hour})')
318+
log.debug(f'create periodic task for {event_definition["type"]} ({min_hits_per_hour}, {max_hits_per_hour})')
317319
# event_callback()
318320
return task
319321

@@ -328,7 +330,7 @@ def __send_event(self, event_fragment, timestamp = None):
328330
base_event.update(event_fragment)
329331

330332
if self.shutdown:
331-
logging.info(f'{self.model["id"]} is down -> ignore event: {json.dumps(base_event)}')
333+
log.info(f'{self.model["id"]} is down -> ignore event: {json.dumps(base_event)}')
332334
return None
333335
else:
334336
cumulocityAPI.send_event(base_event)
@@ -342,13 +344,17 @@ def load(filename):
342344
print(e, type(e))
343345
return {}
344346

347+
log.info(f'cwd:{os.getcwd()}')
345348
SIMULATOR_MODELS = load("simulators.json")
346349

347350
simulators = list(map(lambda model: MachineSimulator(model), SIMULATOR_MODELS))
348351

349352
# create managed object for every simulator
350353
[item.get_or_create_device_id() for item in simulators]
351354

355+
[oeeAPI.create_and_activate_profile(id, ProfileCreateMode.CREATE_IF_NOT_EXISTS)
356+
for id in oeeAPI.get_simulator_external_ids()]
357+
352358
while True:
353359
for simulator in simulators:
354360
simulator.tick()

0 commit comments

Comments
 (0)