diff --git a/src/app/group-monitor/backend.service.ts b/src/app/group-monitor/backend.service.ts
index b1e81f1b14092dd9c1ccfc6f13dc8f5d9ac2eb7b..cd648c9f816adbb13c03c63433d358de45e0d81b 100644
--- a/src/app/group-monitor/backend.service.ts
+++ b/src/app/group-monitor/backend.service.ts
@@ -1,8 +1,11 @@
-import {Injectable} from '@angular/core';
-import {BehaviorSubject, Observable} from 'rxjs';
+import {Inject, Injectable} from '@angular/core';
+import {BehaviorSubject, Observable, of} from 'rxjs';
 import {webSocket, WebSocketSubject} from 'rxjs/webSocket';
 import {WebSocketMessage} from 'rxjs/internal/observable/dom/WebSocketSubject';
-import {filter, map, share} from 'rxjs/operators';
+import {catchError, filter, map, share} from 'rxjs/operators';
+import {TestData} from '../test-controller/test-controller.interfaces';
+import {ApiError} from '../app.interfaces';
+import {HttpClient} from '@angular/common/http';
 
 
 interface WsMessage {
@@ -22,7 +25,10 @@ export class BackendService {
   public serviceConnected$ = new BehaviorSubject<boolean>(undefined);
 
 
-  constructor() {
+  constructor(
+      @Inject('SERVER_URL') private serverUrl: string,
+      private http: HttpClient
+  ) {
 
     this.serviceConnected$
         .pipe(filter((value: boolean) => (value !== undefined)))
@@ -121,4 +127,35 @@ export class BackendService {
         .pipe(map((event: WsMessage): T => event.data))
         .pipe(share());
   }
+
+
+  // === non websocket stuff -> TODO move to separate service
+
+  getTestData(testId: string): Observable<TestData | boolean> {
+
+    console.log("load booklet for " + testId);
+
+    return this.http
+        .get<TestData>(this.serverUrl + 'test/' + testId)
+        .pipe(
+            catchError((err: ApiError) => {
+              console.warn(`getTestData Api-Error: ${err.code} ${err.info} `);
+              return of(false)
+            })
+        );
+
+    //const loadingTestData = new BehaviorSubject<TestData | boolean>(true);
+    // const TODO_unsubscribeMe = this.http
+    //     .get<TestData>(this.serverUrl + 'test/' + testId)
+    //     .pipe(
+    //         catchError((err: ApiError) => {
+    //           console.warn(`getTestData Api-Error: ${err.code} ${err.info} `);
+    //           return of(false)
+    //         })
+    //     )
+    //     .subscribe(loadingTestData);
+    // return loadingTestData;
+  }
+
+
 }
diff --git a/src/app/group-monitor/booklet.service.ts b/src/app/group-monitor/booklet.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..25486a102a061f7d2ba72219787b9eb33f940730
--- /dev/null
+++ b/src/app/group-monitor/booklet.service.ts
@@ -0,0 +1,234 @@
+import {Injectable} from '@angular/core';
+import {Testlet} from '../test-controller/test-controller.classes';
+import {BookletConfig} from '../config/booklet-config';
+import {MainDataService} from '../maindata.service';
+import {BackendService} from './backend.service';
+import {BehaviorSubject, of} from 'rxjs';
+import {isDefined} from '@angular/compiler/src/util';
+import {TestData} from '../test-controller/test-controller.interfaces';
+import {map} from 'rxjs/operators';
+
+// TODO find a solution for shared classes
+
+export interface Booklet {
+    lastUnitSequenceId: number;
+    lastTestletIndex: number;
+    allUnitIds;
+    testlet: Testlet
+    config: BookletConfig
+}
+
+
+
+@Injectable()
+export class BookletService {
+
+
+    public booklets: BehaviorSubject<Booklet>[] = [];
+
+
+    constructor(
+        private bs: BackendService
+    ) { }
+
+
+    public getBooklet(testId: string): BehaviorSubject<Booklet|boolean> {
+
+        if (isDefined(this.booklets[testId])) {
+
+            console.log('FORWARDING testlet data for ' + testId + '');
+            return this.booklets[testId];
+        }
+
+        if (parseInt(testId) < 1) {
+
+            this.booklets[testId] = of(null);
+
+        } else {
+
+            console.log('LOADING testlet data for ' + testId + ' not available. loading');
+
+            const loadingTestData = new BehaviorSubject<Booklet|boolean>(true);
+            const TODO_unsubscribeMe = this.bs.getTestData(testId)
+                .pipe(map((testData: TestData): string => testData.xml))
+                .pipe(map(BookletService.getBookletFromXml))
+                .subscribe(loadingTestData);
+
+            this.booklets[testId] = loadingTestData;
+        }
+
+        return this.booklets[testId];
+    }
+
+
+    // TODO those functions are more or less copies from test.controller.component. avoid duplicate doce
+    private static getBookletFromXml(xmlString: string): Booklet|boolean {
+
+        let rootTestlet: Testlet = null;
+        let booklet: Booklet = null;
+
+        try {
+            const oParser = new DOMParser();
+            const oDOM = oParser.parseFromString(xmlString, 'text/xml');
+            if (oDOM.documentElement.nodeName === 'Booklet') {
+                // ________________________
+                const metadataElements = oDOM.documentElement.getElementsByTagName('Metadata');
+                if (metadataElements.length > 0) {
+                    const metadataElement = metadataElements[0];
+                    const IdElement = metadataElement.getElementsByTagName('Id')[0];
+                    const LabelElement = metadataElement.getElementsByTagName('Label')[0];
+                    rootTestlet = new Testlet(0, IdElement.textContent, LabelElement.textContent);
+                    const unitsElements = oDOM.documentElement.getElementsByTagName('Units');
+                    if (unitsElements.length > 0) {
+                        const customTextsElements = oDOM.documentElement.getElementsByTagName('CustomTexts');
+                        if (customTextsElements.length > 0) {
+                            const customTexts = BookletService.getChildElements(customTextsElements[0]);
+                            const customTextsForBooklet = {};
+                            for (let childIndex = 0; childIndex < customTexts.length; childIndex++) {
+                                if (customTexts[childIndex].nodeName === 'Text') {
+                                    const customTextKey = customTexts[childIndex].getAttribute('key');
+                                    if ((typeof customTextKey !== 'undefined') && (customTextKey !== null)) {
+                                        customTextsForBooklet[customTextKey] = customTexts[childIndex].textContent;
+                                    }
+                                }
+                            }
+                            // this.cts.addCustomTexts(customTextsForBooklet);
+                        }
+
+                        const bookletConfigElements = oDOM.documentElement.getElementsByTagName('BookletConfig');
+
+                        const bookletConfig = new BookletConfig();
+
+                        bookletConfig.setFromKeyValuePairs(MainDataService.getTestConfig());
+                        if (bookletConfigElements.length > 0) {
+                            bookletConfig.setFromXml(bookletConfigElements[0]);
+                        }
+
+                        // this.tcs.testMode = new TestMode(loginMode);
+
+                        // recursive call through all testlets
+
+                        booklet = {
+                            lastUnitSequenceId: 1,
+                            lastTestletIndex: 1,
+                            allUnitIds: [],
+                            testlet: rootTestlet,
+                            config: bookletConfig
+                        };
+
+                        BookletService.addTestletContentFromBookletXml(booklet, unitsElements[0]);
+                    }
+                }
+            }
+        } catch (error) {
+            console.log('error reading booklet XML:');
+            console.log(error);
+
+            booklet = null;
+        }
+
+        if (booklet == null) {
+            return false;
+        }
+
+        return booklet;
+    }
+
+
+    private static getChildElements(element) {
+        return Array.prototype.slice.call(element.childNodes)
+            .filter(function (e) { return e.nodeType === 1; });
+    }
+
+
+    private static addTestletContentFromBookletXml(booklet: Booklet, node: Element) {
+
+        const targetTestlet = booklet.testlet;
+
+        const childElements = BookletService.getChildElements(node);
+        if (childElements.length > 0) {
+            let codeToEnter = '';
+            let codePrompt = '';
+            let maxTime = -1;
+
+            let restrictionElement: Element = null;
+            for (let childIndex = 0; childIndex < childElements.length; childIndex++) {
+                if (childElements[childIndex].nodeName === 'Restrictions') {
+                    restrictionElement = childElements[childIndex];
+                    break;
+                }
+            }
+            if (restrictionElement !== null) {
+                const restrictionElements = BookletService.getChildElements(restrictionElement);
+                for (let childIndex = 0; childIndex < restrictionElements.length; childIndex++) {
+                    if (restrictionElements[childIndex].nodeName === 'CodeToEnter') {
+                        const restrictionParameter = restrictionElements[childIndex].getAttribute('parameter');
+                        if ((typeof restrictionParameter !== 'undefined') && (restrictionParameter !== null)) {
+                            codeToEnter = restrictionParameter.toUpperCase();
+                            codePrompt = restrictionElements[childIndex].textContent;
+                        }
+                    } else if (restrictionElements[childIndex].nodeName === 'TimeMax') {
+                        const restrictionParameter = restrictionElements[childIndex].getAttribute('parameter');
+                        if ((typeof restrictionParameter !== 'undefined') && (restrictionParameter !== null)) {
+                            maxTime = Number(restrictionParameter);
+                            if (isNaN(maxTime)) {
+                                maxTime = -1;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (codeToEnter.length > 0) {
+                targetTestlet.codeToEnter = codeToEnter;
+                targetTestlet.codePrompt = codePrompt;
+            }
+            targetTestlet.maxTimeLeft = maxTime;
+            // if (this.tcs.LastMaxTimerState) {
+            //     if (this.tcs.LastMaxTimerState.hasOwnProperty(targetTestlet.id)) {
+            //         targetTestlet.maxTimeLeft = this.tcs.LastMaxTimerState[targetTestlet.id];
+            //     }
+            // }
+
+            for (let childIndex = 0; childIndex < childElements.length; childIndex++) {
+                if (childElements[childIndex].nodeName === 'Unit') {
+                    const myUnitId = childElements[childIndex].getAttribute('id');
+                    let myUnitAlias = childElements[childIndex].getAttribute('alias');
+                    if (!myUnitAlias) {
+                        myUnitAlias = myUnitId;
+                    }
+                    let myUnitAliasClear = myUnitAlias;
+                    let unitIdSuffix = 1;
+                    while (booklet.allUnitIds.indexOf(myUnitAliasClear) > -1) {
+                        myUnitAliasClear = myUnitAlias + '-' + unitIdSuffix.toString();
+                        unitIdSuffix += 1;
+                    }
+                    booklet.allUnitIds.push(myUnitAliasClear);
+
+                    targetTestlet.addUnit(booklet.lastUnitSequenceId, myUnitId,
+                        childElements[childIndex].getAttribute('label'), myUnitAliasClear,
+                        childElements[childIndex].getAttribute('labelshort'));
+                    booklet.lastUnitSequenceId += 1;
+
+                } else if (childElements[childIndex].nodeName === 'Testlet') {
+                    let testletId: string = childElements[childIndex].getAttribute('id');
+                    if (!testletId) {
+                        testletId = 'Testlet' + booklet.lastTestletIndex.toString();
+                        booklet.lastTestletIndex += 1;
+                    }
+                    let testletLabel: string = childElements[childIndex].getAttribute('label');
+                    testletLabel = testletLabel ? testletLabel.trim() : '';
+
+                    booklet.testlet.addTestlet(testletId, testletLabel);
+                    this.addTestletContentFromBookletXml(booklet, childElements[childIndex]);
+                }
+            }
+        }
+    }
+
+
+}
+
+
+
+
diff --git a/src/app/group-monitor/group-monitor.component.html b/src/app/group-monitor/group-monitor.component.html
index 3210c931635d1f5da645e16e7e7d6c6426100817..55d40ebd7f4ed328a48fc32e7172aa6239f95ddd 100644
--- a/src/app/group-monitor/group-monitor.component.html
+++ b/src/app/group-monitor/group-monitor.component.html
@@ -62,6 +62,13 @@
         </td>
       </ng-container>
 
+      <ng-container matColumnDef="booklet">
+        <th mat-header-cell *matHeaderCellDef>Debug</th>
+        <td mat-cell *matCellDef="let element">
+          <pre>getBookletInfo(element).getValue() | json}}</pre>
+        </td>
+      </ng-container>
+
 
       <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
       <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
diff --git a/src/app/group-monitor/group-monitor.component.ts b/src/app/group-monitor/group-monitor.component.ts
index 83a13a53a00fb42a169bab9c52906689fdccd12b..08282ed23aeb57222c55056a99a6a424268d2669 100644
--- a/src/app/group-monitor/group-monitor.component.ts
+++ b/src/app/group-monitor/group-monitor.component.ts
@@ -1,7 +1,8 @@
 import { Component, OnInit } from '@angular/core';
 import {BackendService} from './backend.service';
-import {Observable} from 'rxjs';
+import {Observable, of} from 'rxjs';
 import {StatusUpdate} from './group-monitor.interfaces';
+import {Booklet, BookletService} from './booklet.service';
 
 @Component({
   selector: 'app-group-monitor',
@@ -10,13 +11,14 @@ import {StatusUpdate} from './group-monitor.interfaces';
 })
 export class GroupMonitorComponent implements OnInit {
 
-  constructor(private bs: BackendService) {
+  constructor(
+      private bs: BackendService,
+      private bookletsService: BookletService,
+  ) {}
 
-  }
-
-  displayedColumns: string[] = ['status', 'name', 'personStatus', 'test', 'testStatus', 'unit', 'unitStatus'];
+  displayedColumns: string[] = ['status', 'name', 'personStatus', 'test', 'testStatus', 'unit', 'unitStatus', 'booklet'];
 
-  dataSource$: Observable<any>;
+  dataSource$: Observable<StatusUpdate[]>;
   clientCount$: Observable<number>;
   serviceConnected$: Observable<boolean>;
 
@@ -34,6 +36,18 @@ export class GroupMonitorComponent implements OnInit {
 
     this.dataSource$ = this.bs.observe<StatusUpdate[]>('status');
 
+    this.dataSource$.subscribe((status: StatusUpdate[]) => {
+      status.forEach((statusUpate: StatusUpdate) => this.getBookletInfo(statusUpate));
+    });
   }
 
+  getBookletInfo(status: StatusUpdate): Observable<Booklet|boolean> {
+
+    if ((typeof status.testState["status"] !== "undefined") && (status.testState["status"] === "locked")) {
+      console.log('no need to load locked booklet', status.testId);
+      return of(null);
+    }
+
+    return this.bookletsService.getBooklet(status.testId.toString());
+  }
 }
diff --git a/src/app/group-monitor/group-monitor.interfaces.ts b/src/app/group-monitor/group-monitor.interfaces.ts
index fa15d26abfa32e0e88ded4de2d018be0c28c8e16..eef09db408e92998aac26e8399286d2cd8e440fb 100644
--- a/src/app/group-monitor/group-monitor.interfaces.ts
+++ b/src/app/group-monitor/group-monitor.interfaces.ts
@@ -10,8 +10,6 @@ export interface StatusUpdate {
     testState: {
         [testStateKey: string]: string
     };
-    testStateKey?: string;
-    testStateValue?: string;
     unitName?: string;
     unitLabel?: string;
     unitState: {
diff --git a/src/app/group-monitor/group-monitor.module.ts b/src/app/group-monitor/group-monitor.module.ts
index 4ecec36fbaa2d9f17f9ee24959105c296cba9e04..a3d06ff78e8cee2433f9ff487b56bc54ea8d31d1 100644
--- a/src/app/group-monitor/group-monitor.module.ts
+++ b/src/app/group-monitor/group-monitor.module.ts
@@ -10,6 +10,7 @@ import { MatChipsModule } from "@angular/material/chips";
 import { CdkTableModule } from '@angular/cdk/table';
 
 import {BackendService} from './backend.service';
+import {BookletService} from './booklet.service';
 
 
 
@@ -27,7 +28,8 @@ import {BackendService} from './backend.service';
       MatChipsModule
   ],
   providers: [
-      BackendService
+      BackendService,
+      BookletService
   ],
 })
 export class GroupMonitorModule {