+/*
+ * Copyright (c) 2010, Titanium Mirror, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * - Neither the name of the Technische Universität Berlin nor the names
+ * of its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Allows multiple clients, who all need to have some underlying component
+ * started, to share the control of a SplitControl (MasterControl) interface.
+ * Any client successfully starting with SplitControl.start() / startDone()
+ * means the MasterControl state is on/started. That state is retained until
+ * the last client successfully completes SplitControl.stop() / stopDone(),
+ * at which time the MasterControl state will be off/stopped.
+ *
+ * @author R. Steve McKown <rsmckown@gmail.com>
+ */
+
+generic module SharedSplitControlP(uint8_t p_clients,
+ uint16_t p_deferStopTime) {
+ provides {
+ interface Init;
+ interface SplitControl[uint8_t id];
+ }
+ uses {
+ interface SplitControl as MasterControl;
+ interface Timer<TMilli>;
+ interface State;
+ }
+}
+implementation {
+ enum {
+ /* MasterControl states */
+ S_IDLE = 0,
+ S_START,
+ S_ON,
+ S_STOPWAIT,
+ S_STOP,
+
+ /* S_STOPWAIT is special, for the master only. After all clients have
+ * stopped, the master could be stopped. But, we leave it on in the
+ * S_STOPWAIT state for a little bit before actually stopping it, so that
+ * we can reduce start/stop thrashing in our application. This behavior
+ * is controlled by the p_deferStopTime parameter.
+ */
+ };
+
+ uint8_t m_state[p_clients];
+
+ command error_t Init.init()
+ {
+ return SUCCESS;
+ }
+
+ task void signalStarts()
+ {
+ unsigned id;
+
+ for (id = 0; id < p_clients; id++) {
+ if (m_state[id] == S_START) {
+ m_state[id] = S_ON;
+ signal SplitControl.startDone[id](SUCCESS);
+ }
+ }
+ }
+
+ command error_t SplitControl.start[uint8_t id]()
+ {
+ error_t error;
+
+ if (id >= p_clients)
+ return FAIL;
+
+ switch (m_state[id]) {
+ case S_IDLE:
+ switch (call State.getState()) {
+ case S_IDLE:
+ error = call MasterControl.start();
+ if (error == SUCCESS) {
+ call State.forceState(S_START);
+ m_state[id] = S_START;
+ }
+ return error;
+ break;
+ case S_START:
+ m_state[id] = S_START;
+ return SUCCESS;
+ break;
+ case S_STOPWAIT:
+ call Timer.stop();
+ call State.forceState(S_ON);
+ /* fall through */
+ case S_ON:
+ m_state[id] = S_START;
+ post signalStarts();
+ return SUCCESS;
+ break;
+ case S_STOP:
+ /* Master will restart as soon as its stop is done */
+ m_state[id] = S_START;
+ return SUCCESS;
+ break;
+ default:
+ /* Should never get here */
+ return FAIL;
+ }
+ break;
+ case S_START:
+ return SUCCESS;
+ break;
+ case S_ON:
+ return EALREADY;
+ break;
+ case S_STOP:
+ default:
+ return EBUSY;
+ break;
+ }
+ }
+
+ event void MasterControl.startDone(error_t error)
+ {
+ unsigned id;
+ unsigned newState = (error == SUCCESS) ? S_ON : S_IDLE;
+
+ /* Master is starting, implying >= 1 client starting and all others are off.
+ * Master and all starting clients transition to the on state if the start
+ * was successful, or fall back to the idle (off) state if not.
+ */
+ call State.forceState(newState);
+ for (id = 0; id < p_clients; id++) {
+ if (m_state[id] == S_START) {
+ m_state[id] = newState;
+ signal SplitControl.startDone[id](error);
+ }
+ }
+ }
+
+ task void signalStops()
+ {
+ unsigned id;
+ bool allOff = TRUE;
+
+ for (id = 0; id < p_clients; id++) {
+ if (m_state[id] == S_STOP) {
+ m_state[id] = S_IDLE;
+ signal SplitControl.stopDone[id](SUCCESS);
+ } else if (m_state[id] != S_IDLE)
+ allOff = FALSE;
+ }
+ if (allOff) {
+ /* All clients are off. We can stop the master, but we might wait a bit
+ * before stopping the master.
+ */
+ if (p_deferStopTime) {
+ call State.forceState(S_STOPWAIT);
+ call Timer.startOneShot(p_deferStopTime);
+ } else {
+ if (call MasterControl.stop() == SUCCESS)
+ call State.forceState(S_STOP);
+ }
+ }
+ }
+
+ command error_t SplitControl.stop[uint8_t id]()
+ {
+ if (id >= p_clients)
+ return FAIL;
+
+ switch (m_state[id]) {
+ case S_IDLE:
+ return EALREADY;
+ break;
+ case S_ON:
+ switch (call State.getState()) {
+ case S_ON:
+ /* fall through */
+ case S_STOPWAIT:
+ case S_IDLE:
+ /* Invalid condition. If the master is off, then all clients
+ * should also be off. Go ahead signal the client to stop.
+ */
+ m_state[id] = S_STOP;
+ post signalStops();
+ return SUCCESS;
+ break;
+ case S_START:
+ case S_STOP:
+ default:
+ /* Invalid condition. For the master to be starting, all clients
+ * must either be off or starting. For the master to be stopping,
+ * no client can be on. We can generally correct this invalid
+ * condition by setting the client to S_STOP and dealing with it
+ * in MasterControl.startDone() or stopDone() (respectively).
+ */
+ m_state[id] = S_STOP;
+ return SUCCESS;
+ break;
+ }
+ break;
+ case S_STOP:
+ return SUCCESS;
+ break;
+ case S_START:
+ default:
+ return EBUSY;
+ break;
+ }
+ /* If we get here, we had an invalid state combination */
+ }
+
+ event void Timer.fired()
+ {
+ error_t error = call MasterControl.stop();
+
+ if (error == SUCCESS)
+ call State.forceState(S_STOP);
+ else {
+ unsigned id;
+
+ /* If we failed to start, we need to signal any starting clients */
+ for (id = 0; id < p_clients; id++) {
+ if (m_state[id] == S_START) {
+ m_state[id] = S_ON;
+ signal SplitControl.startDone[id](SUCCESS);
+ }
+ }
+ }
+ }
+
+ event void MasterControl.stopDone(error_t error)
+ {
+ unsigned id;
+ unsigned newState = (error == SUCCESS) ? S_IDLE : S_ON;
+ bool starts = FALSE;
+
+ /* Master is stopping, implying >= 1 client stopping. Other clients may
+ * be in any other state. Master and all stopping clients transition to
+ * the off state if the start was successful, or fall back to the on state
+ * if not.
+ */
+ call State.forceState(newState);
+ for (id = 0; id < p_clients; id++) {
+ if (m_state[id] == S_STOP) {
+ m_state[id] = newState;
+ signal SplitControl.stopDone[id](error);
+ } else if (m_state[id] == S_START)
+ starts = TRUE;
+ }
+
+ /* While the master was stopping, some clients that were off may have
+ * issued a start. It is possible that we need to start the master again.
+ * If the master start() fails, all clients in start get kicked back to
+ * off, in error.
+ */
+ if (starts) {
+ error = call MasterControl.start();
+
+ if (error == SUCCESS)
+ call State.forceState(S_START);
+ else {
+ for (id = 0; id < p_clients; id++) {
+ if (m_state[id] == S_START) {
+ m_state[id] = S_IDLE;
+ signal SplitControl.startDone[id](error);
+ }
+ }
+ }
+ }
+ }
+
+ default event void SplitControl.startDone[uint8_t id](error_t error) {}
+ default event void SplitControl.stopDone[uint8_t id](error_t error) {}
+}