Hot Tub Gecko inTouch2

Hello,

can somebody help me to connect to my hot tub? There are two big companys out there: balboa and gecko. For the balboa someone already made a solution.

gecko controller and android app and here Gecko Intouch

Found a github phyton script
I found the correct MAC address and spa.srccn from my wifi packages.

#!/usr/bin/python
from intouch2 import intouch2
import logging
spa = intouch2(logging.DEBUG)

# Set the MAC address of the device
spa.Mac = 'xx:xx:xx:xx:xx'
# Source Connection (?) value. Obtain this value from an existing communication from the App (using for example Wireshark)
spa.Srccn = 'XXX413c433-b343-4f24-a432-5ed5c53cb152c'

spa.connect()
print 'Channel: %d' % spa.getCurrentChannel()
print 'Setpoint: %d.  Current Temperature: %d.  Lights: %d.  Version: %s.  Base config: %s.' % spa.getStatus()
spa.disconnect()

and

#!/usr/bin/python
import socket
import logging
import re
from struct import *

class Watercare:
        AwayFromHome, Standard, EnergySaving, Supersaver, Weekender = range(0, 5)

class Messages:
	Hello = '<HELLO>%d</HELLO>'
	GetCurrentChannel = '<PACKT><SRCCN>%s</SRCCN><DESCN>%s</DESCN><DATAS>CURCH%d</DATAS></PACKT>'
	GetWatercare = '<PACKT><SRCCN>%s</SRCCN><DESCN>%s</DESCN><DATAS>GETWC%d</DATAS></PACKT>'
	GetStatus = '<PACKT><SRCCN>%s</SRCCN><DESCN>%s</DESCN><DATAS>STATU%d\x00\x00\x02\x00</DATAS></PACKT>'

class intouch2:
	server_address = ('intouch.geckoal.com', 10022)
        sock = None
        sqn = 1
	Srccn = 'dummy'
	Mac = ''

	def __init__(self, level):
		logging.basicConfig(level=level,
                    format='%(asctime)s %(levelname)s %(message)s',
                    filename='intouch2.log')

	def connect(self):
		logging.info('Connecting to %s:%d' % (self.server_address[0], self.server_address[1]))
        	# Create a UDP socket
        	self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        	# We'll wait up to 1 second for a response
        	self.sock.settimeout(1)
		# Starting sequence number
        	self.sqn = 1

	def disconnect(self):
		logging.info('Disconnecting')
        	self.sock.close();

	def sendWithSrccnMacSqn(self, msg, srccn, mac):
		buf = msg % (srccn, mac, self.sqn)
        	self.sqn = self.sqn + 1	
		return self.send(buf)

	def sendWithSqn(self, msg):
        	buf = msg % (self.sqn)
        	self.sqn = self.sqn + 1	
		return self.send(buf)

	def send(self, msg):
		logging.debug('Sending `%s`' % msg)
        	sent = self.sock.sendto(msg, self.server_address)

	def recv(self):
		logging.debug('Waiting to receive data')
		try:
			data, server = self.sock.recvfrom(4096)
		except:
			logging.error('Timeout! No data received')
	 		return None
		logging.debug('Received `%s`' % data)
		return data

	def sendHello(self):
		self.sendWithSqn(Messages.Hello)
		return self.recv()

	def getCurrentChannel(self):
		logging.debug('Sending GetCurrentChannel message')
	 	self.sendWithSrccnMacSqn(Messages.GetCurrentChannel, self.Srccn, self.Mac)
		data = self.recv()
		m = re.search(r'<DATAS>CHCUR(.*)</DATAS>', data, re.DOTALL)
		(channel, rest) = unpack('>bb', m.group(1))
		return channel

	def getCurrentWatercare(self):
		logging.debug('Sending GetWatercare message')
		self.sendWithSrccnMacSqn(Messages.GetWatercare)
		data = self.recv()
		m = re.search(r'<DATAS>WCGET(.*)</DATAS>', data)
		(value) = unpack('b', m.group(1))
		return Watercare(value[0])

	def getStatus(self):
		logging.debug('Sending GetStatus message')
		self.sendWithSrccnMacSqn(Messages.GetStatus, self.Srccn, self.Mac)
		while True:
			data = self.recv()
			m = re.search(r'<DATAS>STATV(.*)</DATAS>', data, re.DOTALL)
			(seq, next, rest) = unpack('>bb{0}s'.format(len(m.group(1))-2), m.group(1))
			logging.debug('Status message: Seq: %s. Next: %s. Length: %s' % (seq, next, len(rest)))
			#print [hex(ord(c)) for c in rest]
			# If next sequence number is 0, that appears to be the end of the messages
			if (next == 0): 
				break;
			# Some interesting fields are present in the 7th message
			if (seq == 7):
				(f1, f2, f3, setpoint, temp, junk1, YT1, YT2, YT3, config, j1, j2, j3, j4, lights, junk2) = unpack('>bbbhh18shbbhbbbbb4s', rest)
				setpoint = setpoint/10.0+32.0
				temp = temp/10.0+32.0
				version = "inYT %s v%s.%s" % (YT1, YT2, YT3)
		return (setpoint, temp, lights, version, config)

Tried to run the script but I get no response.

Traceback (most recent call last):
File ā€œ./spa.pyā€, line 15, in
print ā€˜Setpoint: %d. Current Temperature: %d. Lights: %d. Version: %s. Base config: %s.ā€™ % spa.getStatus()
File ā€œ/etc/openhab2/html/gecko/intouch2.pyā€, line 91, in getStatus
m = re.search(rā€™STATV(.*)ā€™, data, re.DOTALL)
File ā€œ/usr/lib/python2.7/re.pyā€, line 146, in search
return _compile(pattern, flags).search(string)

and logging file:
2018-12-01 16:56:14,064 INFO Connecting to intouch.geckoal.com:10022
2018-12-01 16:56:14,065 DEBUG Sending GetStatus message
2018-12-01 16:56:14,065 DEBUG Sending <PACKT><SRCCN>XXX413c433-b343-4f24-a432-5ed5c53cb152c/SRCCN><DESCN>xx:98:f4:a1:d0:4b</DESCN><DATAS>STATU1 </DATAS></PACKT>
2018-12-01 16:56:14,264 DEBUG Waiting to receive data
2018-12-01 16:56:29,280 ERROR Timeout! No data received

appreciate any help
Michael

Well, this is fun. Here I am, the one that first decoded the Balboa protocol - and now I (unknowingly) find myself with an inTouch2 controller (in my Mr. Steam shower steam generator, of all things!). https://github.com/chicago6061/in.touch2/issues/1 actually has a lot more details about how the protocol works, and how to control stuff. Iā€™ve captured my steamer getting controlled (it uses a STEAMLINX app, not Gecko, but itā€™s the same protocol), and things match up with whatā€™s in that issue. Unfortunately, controlling my steamer is pretty far down on my list of things to automate, and this is a far more opaque protocol than other things Iā€™m looking at at the moment, so it will be a while before I get around to writing something more functional than that python script. Just wanted to ping this thread though, and note that I do hope to get around to it eventually.

Has anyone made any progress on finding a solution for the Gecko in.touch system. At a minimum I would simply like to monitor water temp.

After a lot of time I was revisiting the github page again.
There is an update https://github.com/chicago6061/in.touch2

Nevertheless Im just unable to get a stable connection.
A couple of times I was able to get the reminders when I was opening the app at the same time. so maybe the .py was sniffing the data from the port without really connecting?

Are there any news about binding of in.touch2 to openhab? Unfortunately I only found some scripts with trials to catch the communication in the app. Iā€™m not an expert of this, I cannot use or develop it further.

Iā€™m sure there is some interest to bind in.touch2 to openhab.

Minimum could be to read the temperature of the spa, this would helo already a lot.

have you found other scripts than the github link I posted?

Unfortunately not. For Balboa it seems to exist more. But I decidedfor gecko, thinking that there will be a script.
But I do not have the skill to adapt your skill accordingly that it is working

I built a Gecko library (https://github.com/gazoodle/geckolib) which Iā€™m planning on consuming in a Home Assistant custom component. It has a command line client test program that Iā€™d appreciate anyone interested taking a look and giving me feedback, especially against different configurations. Iā€™ve got an in.xm2 spa.

3 Likes

Not yet purchased, but Iā€™m likely to get a spa with Gecko in.Touch2 and I would want to do very basic control from openhab.

Iā€™m not a developer - read what is on github - not sure I understand what is possible at this stageā€¦

Iā€™d like to:

  • get current water temperature ā†’ printed to a txt file located in the \openHAB3-conf\html folder
  • get current setpoint temperature ā†’ printed to a txt file located in the \openHAB3-conf\html folder
  • change setpoint temperature to trigger heater/pump on/off

Is this already possible with v0.3.20 from 13 Jul?
If yes, what would the commands be?

Thx.

Would be also very interested in a binding or a step by step instruction how to integrate the gecko library from @gazoodle into OH3

Hi @ccutrer any luck connecting your Mr.Steam unit? I have one bought and found out that the homewizard cant do anything but switch it ON or OFF. A little to expansive for what it does if you would ask me. :slight_smile:

Hope you have found a way to hook it up to your domotica.

Regards

I have not done anything with it yet, no. Been very busy with other home automation projects, and this has been near the bottom of the list.

Thanks for your quick response. No worry, I will keep digging. Hope you will get to the bottom of the list sooner then later. :slight_smile:

Hi,

based on the solution provided by gazoodle (GitHub - gazoodle/geckolib: Library to interface with Gecko Alliance spa pack systems via in.touch2 module) I have created a simple service that pushes the most important values to a broker. It works fine for my in.YT.
As the library was in python, I did not look into a way to create a binding. The second reason was to be in depended from OpenHab life-cycle, there for broker solution.

So far values for water header, with the possibility to set the target temperature, status of light including to switch them on and off, status of pumps, water care and filters and the reminders (only tested with rinse and clean filter, change water and check SPA) are included

Maybe you find this helpful (GitHub - kalinrow/geckoclient: A gecko client to publich Gecko in.touch data on a broker)

If needed, I can also provide you the things, items and rules (for reminders)

Best regards

1 Like

Hi @kalinrow,

Your approach looks very promising :grinning:

My new SPA just arrived, is up and running, works well with the in.Touch2 appā€¦
The SPA is also based on in.YT.

This is the first time Iā€™m going to go about a broker solution in openhab - Iā€™ve never done anything with MQTT in the past.

What is required to be installed, configured, etc. in openhab beyond the elements you describe on GitHub?

MQTT binding? What is the minimum config needed there?

# BROKER values
BROKER_ADDRESS = "192.168.1.100"
BROKER_PORT = 1883
BROKER_USERNAME = "username"
BROKER_PASSWORD = "password"
BROKER_ID = "geckoclient"
  • host: The IP/Hostname of the MQTT broker. Be aware that this binding allows only one bridge / one connection per unique host:port.
  • port: The optional port of the MQTT broker. If none is provided, the typical ports 1883 and 8883 (SSL) are used. Be aware that this binding allows only one bridge / one connection per unique host:port.
  • username: The MQTT username (since MQTT 3.1). Defaults to empty.
  • password: The MQTT password (since MQTT 3.1). Defaults to empty.
  • clientID: Use a fixed client ID. Defaults to empty which means a user ID is generated for this connection.

Would that be the correct mapping? Anything missing?

May I kindlky ask you to share you configs for the in.YT? (things/channels/items)

This would be very helpful indeed.
Thank you.

Has anyone else tried this approach?

I had success implementing GeckoLib and running GeckoShell.run().
This is working fine.

I followed the instructions however gecko.service fails to start.

user@NUC8i3BEK:~$ sudo systemctl status gecko.service
[sudo] password for user: 
ā— gecko.service - Gecko client service
     Loaded: loaded (/etc/systemd/system/gecko.service; enabled; vendor preset: enabled)
     Active: failed (Result: exit-code) since Mon 2021-12-27 17:30:55 CET; 35s ago
    Process: 1600 ExecStart=/usr/bin/python3 /opt/geckoclient/client.py (code=exited, status=1/FAILURE)
   Main PID: 1600 (code=exited, status=1/FAILURE)
 
Dez 27 17:30:55 NUC8i3BEK systemd[1]: gecko.service: Scheduled restart job, restart counter is at 5.
Dez 27 17:30:55 NUC8i3BEK systemd[1]: Stopped Gecko client service.
Dez 27 17:30:55 NUC8i3BEK systemd[1]: gecko.service: Start request repeated too quickly.
Dez 27 17:30:55 NUC8i3BEK systemd[1]: gecko.service: Failed with result 'exit-code'.
Dez 27 17:30:55 NUC8i3BEK systemd[1]: Failed to start Gecko client service.

# Log file
LOGFILE = "/var/log/geckoclient.log"

ā€¦ not even a logfile is being created

Ubuntu reports the following error:

Any idea how to fix this?

Hello Marcus,
glad to read that you have installed the geckolib successfully.

From what I see in the posted error message it seems that you have forgotten to install the paho-mqtt library.

client.py crashed with ModuleNotFoundError in /opt/geckoclient/mqtt.py: No module named ā€˜pahoā€™

Can you please check, if you have installed the needed libraries:

pip install paho-mqtt

You can also run the client manually first, to see any client output by executing:

python3 /opt/geckoclient/client.py

Please do not forget to patch the geckoclient as my mqtt bridge client is need of the additional features.

Unluckily I still fight with getting the target temperature in case it is set directly on the pool. And sometimes setting of the temperature is not working. I hope to be able to improve that in the coming month.

Best regards
Frank

Hi Frank,

glad to see youā€™re back :grinning:

Just re-installed everything - same resultā€¦

Dec 28 21:07:14 NUC8i3BEK systemd[1]: Started Gecko client service.
Dec 28 21:07:14 NUC8i3BEK python3[26828]: Traceback (most recent call last):
Dec 28 21:07:14 NUC8i3BEK python3[26828]:   File "/opt/geckoclient/client.py", line 19, in <module>
Dec 28 21:07:14 NUC8i3BEK python3[26828]:     from mqtt import Mqtt
Dec 28 21:07:14 NUC8i3BEK python3[26828]:   File "/opt/geckoclient/mqtt.py", line 9, in <module>
Dec 28 21:07:14 NUC8i3BEK python3[26828]:     import paho.mqtt.client as paho
Dec 28 21:07:14 NUC8i3BEK python3[26828]: ModuleNotFoundError: No module named 'paho'
Dec 28 21:07:14 NUC8i3BEK systemd[1]: gecko.service: Main process exited, code=exited, status=1/FAILURE
Dec 28 21:07:14 NUC8i3BEK systemd[1]: gecko.service: Failed with result 'exit-code'.
Dec 28 21:07:14 NUC8i3BEK systemd[1]: gecko.service: Scheduled restart job, restart counter is at 5.
Dec 28 21:07:14 NUC8i3BEK systemd[1]: Stopped Gecko client service.
Dec 28 21:07:14 NUC8i3BEK systemd[1]: gecko.service: Start request repeated too quickly.
Dec 28 21:07:14 NUC8i3BEK systemd[1]: gecko.service: Failed with result 'exit-code'.
Dec 28 21:07:14 NUC8i3BEK systemd[1]: Failed to start Gecko client service.

it is installed as ā€œuserā€ not as ā€œrootā€.

user@NUC8i3BEK:~$ pip install geckolib
Collecting geckolib
  Using cached geckolib-0.3.20-py3-none-any.whl (66 kB)
Requirement already satisfied: urllib3 in /usr/lib/python3/dist-packages (from geckolib) (1.25.8)
Requirement already satisfied: python-version>="3.6" in ./.local/lib/python3.8/site-packages (from geckolib) (0.0.2)
Installing collected packages: geckolib
Successfully installed geckolib-0.3.20

user@NUC8i3BEK:~$ pip install paho-mqtt
Processing ./.cache/pip/wheels/6a/48/01/c895c027e9b9367ec5470fbf371ee56e795a49ac6a19aa4c9f/paho_mqtt-1.6.1-py3-none-any.whl
Installing collected packages: paho-mqtt
Successfully installed paho-mqtt-1.6.1

geckoclient files are located in:

user@NUC8i3BEK:/opt/geckoclient$ ls -l
total 28
-rw-rw-r-- 1 root root 9578 Nov 20 16:52 client.py
-rw-rw-r-- 1 root root  632 Dez 27 17:21 config.py
-rw-rw-r-- 1 root root  354 Nov 20 16:52 const.py
-rw-rw-r-- 1 root root 3556 Nov 20 16:52 mqtt.py
drwxr-xr-x 2 root root 4096 Dez 27 18:24 __pycache__
user@NUC8i3BEK:~$ python3 /opt/geckoclient/client.py
user@NUC8i3BEK:~$

edit 22:19
entry in gecko_client.log (btw no log entries when starting gecko.service)

2021-12-28 20:57:46,501 - mqtt - ERROR - Connection error: 111: Connection refused
2021-12-28 20:57:46,502 - geckoclient - ERROR - Stopping - Can't connect to broker

patched at 20:57

user@NUC8i3BEK:~/.local/lib/python3.8/site-packages/geckolib/automation$ ls -l
total 64
-rw-rw-r-- 1 user user   948 Dez 28 20:48 base.py
-rw-rw-r-- 1 user user   197 Dez 28 20:48 blower.py
-rw-rw-r-- 1 user user 10108 Dez 28 20:57 facade.py
-rw-rw-r-- 1 user user  6568 Dez 28 20:48 heater.py
-rw-rw-r-- 1 user user   694 Dez 28 20:57 __init__.py
-rw-rw-r-- 1 user user   300 Dez 28 20:48 keypad.py
-rw-rw-r-- 1 user user   182 Dez 28 20:48 light.py
-rw-rw-r-- 1 user user  1329 Dez 28 20:48 pump.py
drwxrwxr-x 2 user user  4096 Dez 28 20:57 __pycache__
-rw-rw-r-- 1 user user  1748 Dez 28 20:57 reminders.py
-rw-rw-r-- 1 user user  1731 Dez 28 20:48 sensors.py
-rw-rw-r-- 1 user user  1663 Dez 28 20:48 switches.py
-rw-rw-r-- 1 user user  2328 Dez 28 20:48 watercare.py

user@NUC8i3BEK:~/.local/lib/python3.8/site-packages/geckolib/driver/protocol$ ls -l
total 52
-rw-rw-r-- 1 user user 2585 Dez 28 20:48 configfile.py
-rw-rw-r-- 1 user user 1338 Dez 28 20:48 firmware.py
-rw-rw-r-- 1 user user 1844 Dez 28 20:48 getchannel.py
-rw-rw-r-- 1 user user 2374 Dez 28 20:48 hello.py
-rw-rw-r-- 1 user user 1356 Dez 28 20:48 __init__.py
-rw-rw-r-- 1 user user 3255 Dez 28 20:48 packcommand.py
-rw-rw-r-- 1 user user 2786 Dez 28 20:48 packet.py
-rw-rw-r-- 1 user user  830 Dez 28 20:48 ping.py
drwxrwxr-x 2 user user 4096 Dez 28 20:57 __pycache__
-rw-rw-r-- 1 user user 2082 Dez 28 20:57 reminders.py
-rw-rw-r-- 1 user user 4087 Dez 28 20:48 statusblock.py
-rw-rw-r-- 1 user user 1987 Dez 28 20:48 version.py
-rw-rw-r-- 1 user user 2878 Dez 28 20:48 watercare.py

This is a bit strangeā€¦

edit 23:01:
Could be related to config.pyā€¦
What are the correct broker settings?

###########
# configuration of SPA and BROKER
#

# SPA values
SPA_NAME = "Whirlpool"
SPA_IDENTIFIER = "SPA68:27:19:xx:xx:xx"


# Replace with your own UUID, see https://www.uuidgenerator.net/>
CLIENT_ID = "d265xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# BROKER values
BROKER_ADDRESS = "192.168.0.13"
BROKER_PORT = 1883
BROKER_USERNAME = "xxx"
BROKER_PASSWORD = "xxx"
BROKER_ID = "geckoclient"

# Topic
TOPIC = "whirlpool"

# Log file
LOGFILE = "/var/log/geckoclient.log"

# Debug level
# can be one of the following strings
#   'CRITICAL','FATAL', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'NOTSET'
DEBUG_LEVEL = 'INFO'

Broker address would be the IP of the machine running GeckoClient, correct?
Do I need to set BROKER_USERNAME and BROKER_PASSWORD to something specific?

Best regards,
Marcus

Hello,

I took some days off during Christmas period :wink:
That is really a bit strange.

Regarding the Broker, you first need to either install a separate Broker (see [OH3] MQTT Setup and Configuration for details).

Then you need to put either 127.0.0.1 (if the broker and the geckoclient is on the same machine) or the IP of the machine the broker is running on else and depending if you have setup a user and password the values.

But that should not be a problem. At least the client should start, the log file should be created and something should be logged in there. Do you see anything inside when starting the client from the command line?

Best regards
Frank

Hello Frank,

a big step forward! Thanks a lot!

Some success - connection to broker successful and subscribed to water-heater and lights!
However, there is an error message when running client.py.
gecko.service still fails to start with the same message as in my previous post.

Maybe worth to mention:
I use SpaPackStruct.xml v33 (link) to connect to my in.YE-5-H3.6. I couldnā€™t get it running with v19 and v24.
The iOS app refers to in.YT, thatā€™s why I Initially thought that my SPA has an in.YT.

MQTT broker set up as per instruction with the following result:

gecko_client.log:

2021-12-29 11:01:12,194 - mqtt - INFO - Subscribing to whirlpool/water_heater/cmnd
2021-12-29 11:01:12,194 - mqtt - INFO - MQTT successfull connected to broker 127.0.0.1
2021-12-29 11:01:12,194 - mqtt - INFO - Subscribing to whirlpool/lights/cmnd
user@NUC8i3BEK:~$ python3 /opt/geckoclient/client.py
Traceback (most recent call last):
  File "/opt/geckoclient/client.py", line 324, in <module>
    json = get_pumps_payload()
  File "/opt/geckoclient/client.py", line 89, in get_pumps_payload
    json += f'"{facade.pumps[2].name}":"{facade.pumps[2].mode}",'
IndexError: list index out of range

Output when running GeckoShell.run():

user@NUC8i3BEK:~$ python
Python 3.8.10 (default, Nov 26 2021, 20:14:08) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from geckolib import GeckoShell
>>> GeckoShell.run()


        <Disclaimer>
        ----------------------------- USE AT YOUR OWN RISK -----------------------------

        This code will allow you to make changes to your spa configuration that is
        outside of what the app, top panel and side panel settings allow. I've not
        tested every setting and it might be that you prevent your spa pack from
        operating as it used to do.

        Configuration is declared in the file SpaPackStruct.xml which is downloaded the
        first time you run this program. Settings marked as RW="ALL" seem to indicate
        that any process can write them, so you ought to be able to revert the settings
        to their original ones.

        I strongly suggest dumping the configuration values with the "config" command
        and recording them somewhere safe.

        </Disclaimer>

    
Starting discovery process...Found 1 spas
Connecting to spa `Whirlpool` at 192.168.0.8 ... connected!
Heater: Temperature 38.0Ā°C, SetPoint 38.0Ā°C, Real SetPoint 38.0Ā°C, Operation Idle
Waterfall: ON
Pump 1: OFF
Blower: OFF
Lights: OFF
WaterCare: Waiting...
Smart Winter Mode:Risk = NO
Circulating Pump = ON
Ozone = OFF
Smart Winter Mode:Active = False
Filter Status:Clean = True
Filter Status:Purge = False
Welcome to the Gecko shell. Type help or ? to list commands.

Whirlpool$ state
Heater: Temperature 38.0Ā°C, SetPoint 38.0Ā°C, Real SetPoint 38.0Ā°C, Operation Idle
Waterfall: ON
Pump 1: OFF
Blower: OFF
Lights: OFF
('Time', '29.12.2021, 11:16:52')
('RinseFilter', 23)
('CleanFilter', 53)
('ChangeWater', 83)
('CheckSpa', 723)
WaterCare: Standard
Smart Winter Mode:Risk = NO
Circulating Pump = ON
Ozone = OFF
Smart Winter Mode:Active = False
Filter Status:Clean = True
Filter Status:Purge = False

gecko.service:
As far as I know services in systemd are started as root whereas geckolib and paho-mqtt are installed in the user directory (site-packages). Maybe the path to paho.mqtt.client is unknown when running:

user@NUC8i3BEK:~$ sudo systemctl start gecko.service

Why not share your things/channels/items while troubleshooting errors and gecko.serviceā€¦ it would probably help to have the end2end view for the stuff for which the subscription worked.

Best regards,
Marcus