#include "Arduino.h"
#include "OneWireSlave.h"

// This is the pin that will be used for one-wire data (depending on your arduino model, you are limited to a few choices, because some pins don't have complete interrupt support)
// On Arduino Uno, you can use pin 2 or pin 3
Pin oneWireData(2);

Pin led(13);

// This is the ROM the arduino will respond to, make sure it doesn't conflict with another device
const byte owROM[7] = { 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 };

// This sample emulates a DS18B20 device (temperature sensor), so we start by defining the available commands
const byte DS18B20_START_CONVERSION = 0x44;
const byte DS18B20_READ_SCRATCHPAD = 0xBE;
const byte DS18B20_WRITE_SCRATCHPAD = 0x4E;

// TODO:
// - handle configuration (resolution, alarms)

enum DeviceState
{
	DS_WaitingReset,
	DS_WaitingCommand,
	DS_ConvertingTemperature,
	DS_TemperatureConverted,
};
volatile DeviceState state = DS_WaitingReset;

// scratchpad, with the CRC byte at the end
volatile byte scratchpad[9];

volatile unsigned long conversionStartTime = 0;

// This function will be called each time the OneWire library has an event to notify (reset, error, byte received)
void owReceive(OneWireSlave::ReceiveEvent evt, byte data);

void setup()
{
	led.outputMode();
	led.writeLow();

	// Setup the OneWire library
	OWSlave.setReceiveCallback(&owReceive);
	OWSlave.begin(owROM, oneWireData.getPinNumber());
}

void loop()
{
	delay(10);

	cli();//disable interrupts
	// Be sure to not block interrupts for too long, OneWire timing is very tight for some operations. 1 or 2 microseconds (yes, microseconds, not milliseconds) can be too much depending on your master controller, but then it's equally unlikely that you block exactly at the moment where it matters.
	// This can be mitigated by using error checking and retry in your high-level communication protocol. A good thing to do anyway.
	DeviceState localState = state;
	unsigned long localConversionStartTime = conversionStartTime;
	sei();//enable interrupts

	if (localState == DS_ConvertingTemperature && millis() > localConversionStartTime + 750)
	{
		float temperature = 42.0f; // here you could plug any logic you want to return the emulated temperature
		int16_t raw = (int16_t)(temperature * 16.0f + 0.5f);

		byte data[9];
		data[0] = (byte)raw;
		data[1] = (byte)(raw >> 8);
		for (int i = 2; i < 8; ++i)
			data[i] = 0;
		data[8] = OWSlave.crc8(data, 8);

		cli();
		memcpy((void*)scratchpad, data, 9);
		state = DS_TemperatureConverted;
		OWSlave.beginWriteBit(1, true); // now that conversion is finished, start sending ones until reset
		sei();
	}
}

void owReceive(OneWireSlave::ReceiveEvent evt, byte data)
{
	switch (evt)
	{
	case OneWireSlave::RE_Byte:
		switch (state)
		{
		case DS_WaitingCommand:
			switch (data)
			{
			case DS18B20_START_CONVERSION:
				state = DS_ConvertingTemperature;
				conversionStartTime = millis();
				OWSlave.beginWriteBit(0, true); // send zeros as long as the conversion is not finished
				break;

			case DS18B20_READ_SCRATCHPAD:
				state = DS_WaitingReset;
				OWSlave.beginWrite((const byte*)scratchpad, 9, 0);
				break;

			case DS18B20_WRITE_SCRATCHPAD:
				
				break;
			}
			break;
		}
		break;

	case OneWireSlave::RE_Reset:
		state = DS_WaitingCommand;
		break;

	case OneWireSlave::RE_Error:
		state = DS_WaitingReset;
		break;
	}
}