aboutsummaryrefslogtreecommitdiff
path: root/main/i2c.c
blob: 6e7614126ede12b82c97cfdef21ff6c6f61eacb9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <FreeRTOS.h>
#include <hardware/i2c.h>
#include <pico/stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <task.h>

#include "config.h"
#include "i2c.h"
#include "pb-buf.h"
#include "pb-mod.h"
#include "pb-send.h"

//! Puzzle module handle
typedef struct {
	i2c_addr_t sender; //!< I2C address of sender
	pb_global_state_t state; //!< global state
} puzzle_module_t;

static pb_global_state_t _global_state = PB_GS_IDLE;
puzzle_module_t modules[CFG_PB_MOD_MAX];
// stolen from lib/pico-sdk/src/rp2_common/hardware_i2c/i2c.c
#define i2c_reserved_addr(addr) \
	(((addr) & 0x78) == 0 || ((addr) & 0x78) == 0x78)
size_t modules_size = 0;

static void bus_scan() {
	pb_buf_t buf = pb_send_magic_req();

	// check for all 7-bit addresses
	uint16_t addr_max = 1 << 7;
	for (uint16_t addr = 0x00; addr < addr_max; addr++) {
		if (i2c_reserved_addr(addr)) continue;
		if (addr == PB_MOD_ADDR) continue;

		pb_i2c_send(addr, (uint8_t *) buf.data, buf.size);
	}

	pb_buf_free(&buf);
}

static void update_state() {
	int idle = 0, playing = 0, solved = 0;

	// count # of modules in each state
	for (size_t i = 0; i < modules_size; i++) {
		pb_global_state_t state = modules[i].state;
		if (state == PB_GS_IDLE) idle++;
		else if (state == PB_GS_PLAYING) playing++;
		else if (state == PB_GS_SOLVED) solved++;
	}

	if (idle == modules_size) { // if all modules are in PB_GS_IDLE
		pb_hook_mod_state_write(PB_GS_IDLE);
	} else if (solved == modules_size) { // if all modules are in PB_GS_SOLVED
		pb_hook_mod_state_write(PB_GS_SOLVED);
	} else {
		pb_hook_mod_state_write(PB_GS_PLAYING);
	}

	// if a module is still playing, don't promote a next one to playing module
	if (playing > 0) return;

	size_t next_idx;
	uint8_t first_addr = (1 << 7);
	for (size_t i = 0; i < modules_size; i++) {
		// find first module that is idle
		pb_global_state_t module_state = modules[i].state;
		if (module_state != PB_GS_IDLE) continue;

		if (modules[i].sender > first_addr) continue;

		first_addr = modules[i].sender;
		next_idx = i;
	}

	pb_buf_t buff = pb_send_state_set(PB_GS_PLAYING);
	pb_i2c_send(modules[next_idx].sender, (uint8_t *) buff.data, buff.size);
	pb_buf_free(&buff);
}

static void state_exchange() {
	update_state();
	pb_buf_t buf = pb_send_state_req();
	for (size_t i = 0; i < modules_size; i++)
		pb_i2c_send(modules[i].sender, (uint8_t *) buf.data, buf.size);
	pb_buf_free(&buf);
}

void bus_task() {
	// do a scan of the bus
	bus_scan();

	while (1) {
		// send my state to all puzzle modules
		state_exchange();

		// wait 1 second
		vTaskDelay(1e3 / portTICK_PERIOD_MS);
	}
}

/**
 * \ingroup main_pb_override
 * \anchor main_route_cmd_magic_res
 *
 * This function registers the I2C address of the puzzle module that replied to
 * the \c MAGIC \c REQ command into a list of "known puzzle modules", which are
 * then periodically updated during gameplay.
 *
 * \note Up to \ref CFG_PB_MOD_MAX puzzle modules can be registered
 * simultaniously.
 */
void pb_route_cmd_magic_res(pb_msg_t * msg) {
	if (modules_size == CFG_PB_MOD_MAX) return;
	modules[modules_size++] = (puzzle_module_t){
		.sender = msg->sender,
		.state = PB_GS_NOINIT,
	};
	printf("i2c: registered puzzle module w/ address 0x%02x\n", msg->sender);
}

void pb_route_cmd_state_res(pb_msg_t * msg) {
	pb_cmd_state_t * cmd = msg->cmd;
	i2c_addr_t sender = msg->sender;

	for (size_t i = 0; i < modules_size; i++) {
		if (modules[i].sender != sender) continue;
		modules[i].state = cmd->state;
	}
}

pb_global_state_t pb_hook_mod_state_read() { return _global_state; }

void pb_hook_mod_state_write(pb_global_state_t state) { _global_state = state; }